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,61 @@
1
+ """IOC namespace — indicator lookup, hash, phishing, bulk."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from urllib.parse import quote
6
+
7
+ from .._transport import _AsyncTransport, _SyncTransport
8
+ from ..models import BulkIocResponse, HashResponse, IocResponse, PhishingResponse
9
+
10
+
11
+ def _enc(value: str) -> str:
12
+ if value is None or value == "":
13
+ raise ValueError("Missing required parameter")
14
+ return quote(str(value), safe="")
15
+
16
+
17
+ def _enc_path(value: str) -> str:
18
+ """For URL-typed indicators (paths) — preserve `/` as a separator."""
19
+ if value is None or value == "":
20
+ raise ValueError("Missing required parameter")
21
+ return "/".join(quote(seg, safe="") for seg in str(value).split("/"))
22
+
23
+
24
+ def _validate_bulk(items: list[str]) -> list[str]:
25
+ if not isinstance(items, list) or not all(isinstance(i, str) for i in items):
26
+ raise ValueError("indicators must be a list of strings")
27
+ return items
28
+
29
+
30
+ class Ioc:
31
+ def __init__(self, transport: _SyncTransport) -> None:
32
+ self._t = transport
33
+
34
+ def lookup(self, indicator: str) -> IocResponse:
35
+ return self._t.get(f"/v1/ioc/{_enc_path(indicator)}")
36
+
37
+ def hash(self, file_hash: str) -> HashResponse:
38
+ return self._t.get(f"/v1/hash/{_enc(file_hash)}")
39
+
40
+ def phishing(self, url: str) -> PhishingResponse:
41
+ return self._t.get(f"/v1/phishing/{_enc_path(url)}")
42
+
43
+ def bulk(self, indicators: list[str]) -> BulkIocResponse:
44
+ return self._t.post("/v1/iocs/bulk", json_body={"indicators": _validate_bulk(indicators)})
45
+
46
+
47
+ class AsyncIoc:
48
+ def __init__(self, transport: _AsyncTransport) -> None:
49
+ self._t = transport
50
+
51
+ async def lookup(self, indicator: str) -> IocResponse:
52
+ return await self._t.aget(f"/v1/ioc/{_enc_path(indicator)}")
53
+
54
+ async def hash(self, file_hash: str) -> HashResponse:
55
+ return await self._t.aget(f"/v1/hash/{_enc(file_hash)}")
56
+
57
+ async def phishing(self, url: str) -> PhishingResponse:
58
+ return await self._t.aget(f"/v1/phishing/{_enc_path(url)}")
59
+
60
+ async def bulk(self, indicators: list[str]) -> BulkIocResponse:
61
+ return await self._t.apost("/v1/iocs/bulk", json_body={"indicators": _validate_bulk(indicators)})
@@ -0,0 +1,94 @@
1
+ """Code-security + live-scan namespaces."""
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
+ CheckHeadersResponse,
11
+ CodeCheckResponse,
12
+ DependenciesResponse,
13
+ ScanHeadersResponse,
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 _validate_packages(packages: list[str]) -> list[str]:
24
+ if not isinstance(packages, list) or not all(isinstance(p, str) for p in packages):
25
+ raise ValueError("packages must be a list of strings")
26
+ return packages
27
+
28
+
29
+ def _validate_headers(headers: dict[str, str]) -> dict[str, str]:
30
+ if not isinstance(headers, dict):
31
+ raise ValueError("headers must be a dict[str, str]")
32
+ return headers
33
+
34
+
35
+ class Check:
36
+ def __init__(self, transport: _SyncTransport) -> None:
37
+ self._t = transport
38
+
39
+ def secrets(self, code: str, language: str | None = None) -> CodeCheckResponse:
40
+ body: dict[str, Any] = {"code": code}
41
+ if language is not None:
42
+ body["language"] = language
43
+ return self._t.post("/v1/check/secrets", json_body=body)
44
+
45
+ def injection(self, code: str, language: str | None = None) -> CodeCheckResponse:
46
+ body: dict[str, Any] = {"code": code}
47
+ if language is not None:
48
+ body["language"] = language
49
+ return self._t.post("/v1/check/injection", json_body=body)
50
+
51
+ def headers(self, headers: dict[str, str]) -> CheckHeadersResponse:
52
+ return self._t.post("/v1/check/headers", json_body={"headers": _validate_headers(headers)})
53
+
54
+ def dependencies(self, packages: list[str]) -> DependenciesResponse:
55
+ return self._t.post("/v1/check/dependencies", json_body={"packages": _validate_packages(packages)})
56
+
57
+
58
+ class Scan:
59
+ def __init__(self, transport: _SyncTransport) -> None:
60
+ self._t = transport
61
+
62
+ def headers(self, domain: str) -> ScanHeadersResponse:
63
+ return self._t.get(f"/v1/scan/headers/{_enc(domain)}")
64
+
65
+
66
+ class AsyncCheck:
67
+ def __init__(self, transport: _AsyncTransport) -> None:
68
+ self._t = transport
69
+
70
+ async def secrets(self, code: str, language: str | None = None) -> CodeCheckResponse:
71
+ body: dict[str, Any] = {"code": code}
72
+ if language is not None:
73
+ body["language"] = language
74
+ return await self._t.apost("/v1/check/secrets", json_body=body)
75
+
76
+ async def injection(self, code: str, language: str | None = None) -> CodeCheckResponse:
77
+ body: dict[str, Any] = {"code": code}
78
+ if language is not None:
79
+ body["language"] = language
80
+ return await self._t.apost("/v1/check/injection", json_body=body)
81
+
82
+ async def headers(self, headers: dict[str, str]) -> CheckHeadersResponse:
83
+ return await self._t.apost("/v1/check/headers", json_body={"headers": _validate_headers(headers)})
84
+
85
+ async def dependencies(self, packages: list[str]) -> DependenciesResponse:
86
+ return await self._t.apost("/v1/check/dependencies", json_body={"packages": _validate_packages(packages)})
87
+
88
+
89
+ class AsyncScan:
90
+ def __init__(self, transport: _AsyncTransport) -> None:
91
+ self._t = transport
92
+
93
+ async def headers(self, domain: str) -> ScanHeadersResponse:
94
+ return await self._t.aget(f"/v1/scan/headers/{_enc(domain)}")
@@ -0,0 +1,182 @@
1
+ """High-level convenience helpers that compose multiple namespace calls.
2
+
3
+ These wrap common multi-call patterns so callers don't have to glue endpoints
4
+ together by hand. Each helper accepts a `ContrastAPI` (sync) instance; async
5
+ variants are intentionally omitted in this batch — call the async client's
6
+ namespace methods directly with `asyncio.gather` if you need parallelism.
7
+
8
+ Convention: shortcuts swallow per-leg ContrastAPIError instances and report
9
+ them in the returned dict under `errors[<key>]` so a partial failure doesn't
10
+ nuke the whole report. The caller still gets every leg that succeeded.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import ipaddress
16
+ import re
17
+ from typing import Any
18
+
19
+ from .client import ContrastAPI
20
+ from .exceptions import ContrastAPIError, TransportError
21
+
22
+ _HEX64 = re.compile(r"^[0-9a-fA-F]{32,128}$")
23
+
24
+
25
+ def _record_or_reraise(out: dict[str, Any], key: str, exc: ContrastAPIError) -> None:
26
+ """Record a per-leg failure into the helper's error dict — but propagate
27
+ `TransportError` (network/MITM/DNS-poison) so security incidents aren't
28
+ masked as opaque error strings. Application-layer errors (4xx/5xx) keep
29
+ the swallow-and-report behaviour callers depend on for partial-success
30
+ reports.
31
+ """
32
+ if isinstance(exc, TransportError):
33
+ raise exc
34
+ out["errors"][key] = exc.message
35
+
36
+
37
+ def _classify_ioc(value: str) -> str:
38
+ """Best-effort IOC type detection: 'ip' | 'hash' | 'domain' | 'unknown'.
39
+
40
+ Mirrors the server's input router so callers see the same routing the API
41
+ would apply. Domain is the broad fallback (anything with a dot or letters).
42
+ """
43
+ value = value.strip()
44
+ try:
45
+ ipaddress.ip_address(value)
46
+ return "ip"
47
+ except ValueError:
48
+ pass
49
+ if _HEX64.match(value):
50
+ return "hash"
51
+ if "." in value or value.replace("-", "").isalnum():
52
+ return "domain"
53
+ return "unknown"
54
+
55
+
56
+ def triage_ioc(client: ContrastAPI, indicator: str) -> dict[str, Any]:
57
+ """Run the right enrichment for a single IOC.
58
+
59
+ For an IP: ioc_lookup + threat_report.
60
+ For a domain: ioc_lookup + domain_report.
61
+ For a hash: ioc_lookup + hash_lookup.
62
+ Any unrecognised input still gets ioc_lookup (the server is the final word).
63
+
64
+ Returns:
65
+ {"indicator": ..., "kind": ..., "ioc": {...}, "threat_report"|"domain_report"|"hash": {...},
66
+ "errors": {<leg>: <error message>}}
67
+ """
68
+ kind = _classify_ioc(indicator)
69
+ out: dict[str, Any] = {"indicator": indicator, "kind": kind, "errors": {}}
70
+
71
+ try:
72
+ out["ioc"] = client.ioc.lookup(indicator)
73
+ except ContrastAPIError as exc:
74
+ _record_or_reraise(out, "ioc", exc)
75
+
76
+ if kind == "ip":
77
+ try:
78
+ out["threat_report"] = client.ip.threat_report(indicator)
79
+ except ContrastAPIError as exc:
80
+ _record_or_reraise(out, "threat_report", exc)
81
+ elif kind == "domain":
82
+ try:
83
+ out["domain_report"] = client.domain.report(indicator)
84
+ except ContrastAPIError as exc:
85
+ _record_or_reraise(out, "domain_report", exc)
86
+ elif kind == "hash":
87
+ try:
88
+ out["hash"] = client.ioc.hash(indicator)
89
+ except ContrastAPIError as exc:
90
+ _record_or_reraise(out, "hash", exc)
91
+
92
+ return out
93
+
94
+
95
+ def audit_full(
96
+ client: ContrastAPI,
97
+ domain: str,
98
+ *,
99
+ ssl_subdomains: int = 5,
100
+ ) -> dict[str, Any]:
101
+ """Run a deep audit on a domain.
102
+
103
+ Composes `audit_domain` + `subdomain_enum` (top-N) + `tech_fingerprint` +
104
+ per-subdomain `ssl_check` (capped at `ssl_subdomains` to keep credit cost
105
+ bounded — default 5).
106
+
107
+ Returns a dict with `audit`, `subdomains`, `tech`, `ssl` (per-subdomain
108
+ map), and `errors` (per-leg failure messages).
109
+ """
110
+ if ssl_subdomains < 0:
111
+ raise ValueError("ssl_subdomains must be >= 0")
112
+
113
+ out: dict[str, Any] = {"domain": domain, "errors": {}, "ssl": {}}
114
+
115
+ try:
116
+ out["audit"] = client.domain.audit(domain)
117
+ except ContrastAPIError as exc:
118
+ _record_or_reraise(out, "audit", exc)
119
+
120
+ subdomains: list[str] = []
121
+ try:
122
+ sub_response = client.domain.subdomains(domain)
123
+ out["subdomains"] = sub_response
124
+ # Server response shape: {"subdomains": [...], "count": N, ...}
125
+ candidates = sub_response.get("subdomains") or []
126
+ subdomains = [s for s in candidates if isinstance(s, str)][:ssl_subdomains]
127
+ except ContrastAPIError as exc:
128
+ _record_or_reraise(out, "subdomains", exc)
129
+
130
+ try:
131
+ out["tech"] = client.domain.tech(domain)
132
+ except ContrastAPIError as exc:
133
+ _record_or_reraise(out, "tech", exc)
134
+
135
+ for sub in subdomains:
136
+ try:
137
+ out["ssl"][sub] = client.domain.ssl(sub)
138
+ except ContrastAPIError as exc:
139
+ _record_or_reraise(out, f"ssl:{sub}", exc)
140
+
141
+ return out
142
+
143
+
144
+ def enrich_batch(client: ContrastAPI, items: list[str]) -> dict[str, Any]:
145
+ """Auto-detect IOC vs CVE for each item and route to the right bulk endpoint.
146
+
147
+ Items matching the `CVE-YYYY-NNNN+` pattern go to `cve.bulk`; everything
148
+ else goes to `ioc.bulk`. If both buckets are non-empty the helper makes two
149
+ calls; if only one bucket has items it makes a single call. Returns:
150
+
151
+ {"cve": <bulk_cve_response or None>, "ioc": <bulk_ioc_response or None>,
152
+ "routed": {"cve": [...], "ioc": [...]}, "errors": {...}}
153
+ """
154
+ if not isinstance(items, list) or not all(isinstance(i, str) for i in items):
155
+ raise ValueError("items must be a list of strings")
156
+
157
+ # Match server-side validator: uppercase canonical, NVD spec allows 3+
158
+ # digit suffix (e.g. CVE-2024-123 is valid). Lowercase routes to IOC bucket.
159
+ cve_pattern = re.compile(r"^CVE-\d{4}-\d{3,}$")
160
+ cves = [i for i in items if cve_pattern.match(i)]
161
+ iocs = [i for i in items if not cve_pattern.match(i)]
162
+
163
+ out: dict[str, Any] = {
164
+ "cve": None,
165
+ "ioc": None,
166
+ "routed": {"cve": cves, "ioc": iocs},
167
+ "errors": {},
168
+ }
169
+
170
+ if cves:
171
+ try:
172
+ out["cve"] = client.cve.bulk(cves)
173
+ except ContrastAPIError as exc:
174
+ _record_or_reraise(out, "cve", exc)
175
+
176
+ if iocs:
177
+ try:
178
+ out["ioc"] = client.ioc.bulk(iocs)
179
+ except ContrastAPIError as exc:
180
+ _record_or_reraise(out, "ioc", exc)
181
+
182
+ return out
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.4
2
+ Name: contrastapi
3
+ Version: 1.22.3
4
+ Summary: Official Python SDK for ContrastAPI — security intelligence for developers and AI agents
5
+ Project-URL: Homepage, https://contrastcyber.com
6
+ Project-URL: Documentation, https://api.contrastcyber.com/docs
7
+ Project-URL: Repository, https://github.com/UPinar/contrastapi
8
+ Project-URL: Issues, https://github.com/UPinar/contrastapi/issues
9
+ Author-email: ContrastCyber <contact@contrastcyber.com>
10
+ License-Expression: MIT
11
+ Keywords: ai-security,api,atlas,attack,cve,cwe,d3fend,dns,domain,kev,mcp,mitre,osint,security,ssl,threat-intelligence,vulnerability,whois
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Security
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: httpx<1.0,>=0.25
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
27
+ Requires-Dist: pytest>=7; extra == 'test'
28
+ Requires-Dist: respx>=0.20; extra == 'test'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # ContrastAPI Python SDK
32
+
33
+ Official Python client for [ContrastAPI](https://api.contrastcyber.com) — security intelligence for developers and AI agents.
34
+
35
+ 42 MCP tools / 50+ HTTP endpoints: CVE / EPSS / KEV / CWE, MITRE ATLAS (AI/ML attacks) + bulk drill, MITRE D3FEND defenses, domain audit, IOC + threat intel, OSINT, code-security checks, and more. No API key required for the free tier (100 req/hr).
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pip install contrastapi
41
+ ```
42
+
43
+ Requires Python 3.10+. Depends only on `httpx>=0.25`.
44
+
45
+ ## Quickstart
46
+
47
+ ### Sync
48
+
49
+ ```python
50
+ from contrastapi import ContrastAPI
51
+ from contrastapi.models import CveResponse, AuditResponse
52
+
53
+ with ContrastAPI() as client: # keyless, free tier
54
+ cve: CveResponse = client.cve.lookup("CVE-2021-44228")
55
+ print(cve["kev"]["in_kev"]) # True (IDE autocompletes "kev", "epss"...)
56
+
57
+ techs = client.atlas.bulk_technique_lookup(["AML.T0051", "AML.T0043"])
58
+ print(techs["successful"]) # 2
59
+
60
+ audit: AuditResponse = client.domain.audit("example.com")
61
+ print(audit["score"])
62
+ ```
63
+
64
+ Every response method has a typed return — `client.cve.lookup(...)` returns `CveResponse`, `client.atlas.technique(...)` returns `AtlasTechniqueResponse`, etc. You never need to write the type annotation; IDEs (VSCode/PyCharm) infer it from the method signature and offer autocomplete on response keys. Import from `contrastapi.models` only if you want to annotate explicitly or pass responses across function boundaries.
65
+
66
+ ### Async
67
+
68
+ ```python
69
+ import asyncio
70
+ from contrastapi import AsyncContrastAPI
71
+
72
+ async def main():
73
+ async with AsyncContrastAPI(api_key="cc_...") as client:
74
+ defenses = await client.d3fend.defense_for_attack("T1059")
75
+ print(len(defenses["defenses"]))
76
+
77
+ asyncio.run(main())
78
+ ```
79
+
80
+ ### Shortcuts (multi-call helpers)
81
+
82
+ ```python
83
+ from contrastapi import ContrastAPI, audit_full, enrich_batch, triage_ioc
84
+
85
+ with ContrastAPI() as client:
86
+ # Auto-route IP/hash/domain to the right enrichment leg
87
+ report = triage_ioc(client, "8.8.8.8") # ioc + threat_report
88
+
89
+ # Audit + subdomains + tech + per-subdomain SSL (capped)
90
+ audit = audit_full(client, "example.com", ssl_subdomains=5)
91
+
92
+ # Auto-detect CVE vs IOC and bulk-route
93
+ enriched = enrich_batch(client, ["CVE-2021-44228", "8.8.8.8", "evil.com"])
94
+ ```
95
+
96
+ Shortcuts swallow per-leg `ContrastAPIError` so partial failures still return whatever succeeded — see `result["errors"]` for the failure map.
97
+
98
+ ## Authentication
99
+
100
+ Pass an API key as the first positional argument or as `api_key=`:
101
+
102
+ ```python
103
+ client = ContrastAPI("cc_<your-key>") # 1000 req/hr (Pro tier)
104
+ ```
105
+
106
+ Get a key at [contrastcyber.com/pricing](https://contrastcyber.com/pricing).
107
+
108
+ ## Exception model
109
+
110
+ The SDK maps server error codes (v1.22.2+ wire envelope) to typed exceptions:
111
+
112
+ | Exception | Status | Server code |
113
+ | --- | --- | --- |
114
+ | `InvalidArgumentError` | 400, 422 | `invalid_argument` |
115
+ | `AuthRequiredError` | 401 | `auth_required` |
116
+ | `TierLimitError` | 403 | `tier_limit` |
117
+ | `NotFoundError` | 404 | `not_found` |
118
+ | `RateLimitError` | 429 | `rate_limit_exceeded` |
119
+ | `UpstreamError` | 502 | `upstream_error` |
120
+ | `UpstreamTimeoutError` | 504 | `upstream_timeout` |
121
+ | `TransportError` | n/a | (network failure, before HTTP) |
122
+ | `ContrastAPIError` | * | base / unknown |
123
+
124
+ Every exception carries the parsed envelope:
125
+
126
+ ```python
127
+ from contrastapi import ContrastAPI, RateLimitError
128
+
129
+ try:
130
+ client.cve.lookup("CVE-2021-44228")
131
+ except RateLimitError as exc:
132
+ print(exc.message) # "Hourly limit reached"
133
+ print(exc.retry_after_seconds) # 60 (capped at 3600)
134
+ print(exc.upgrade_url) # "https://contrastcyber.com/pricing"
135
+ print(exc.extras) # back-compat top-level fields (tier, limit, ...)
136
+ ```
137
+
138
+ ## Namespaces
139
+
140
+ | Namespace | Methods |
141
+ | --- | --- |
142
+ | `cve` | `lookup`, `search`, `leading`, `kev`, `exploit`, `bulk` |
143
+ | `cwe` | `lookup` |
144
+ | `ioc` | `lookup`, `hash`, `phishing`, `bulk` |
145
+ | `atlas` | `technique`, `technique_search`, `bulk_technique_lookup`, `case_study`, `case_study_search` |
146
+ | `d3fend` | `defense`, `defense_search`, `defense_for_attack`, `coverage` |
147
+ | `domain` | `report`, `dns`, `whois`, `subdomains`, `certs`, `ssl`, `tech`, `threat`, `monitor`, `vulns`, `audit`, `wayback`, `bulk` |
148
+ | `ip` | `lookup`, `threat_report` |
149
+ | `asn` | `lookup` |
150
+ | `email` | `mx`, `disposable` |
151
+ | `phone` | `lookup` |
152
+ | `password` | `check` (k-anonymity SHA-1 prefix) |
153
+ | `username` | `lookup` |
154
+ | `check` | `secrets`, `injection`, `headers`, `dependencies` |
155
+ | `scan` | `headers` (live HTTP scan) |
156
+
157
+ The async client (`AsyncContrastAPI`) exposes the same namespace surface 1:1 — every method is `async def`.
158
+
159
+ ## Parity with the Node SDK
160
+
161
+ | Surface | Node SDK | Python SDK |
162
+ | --- | --- | --- |
163
+ | Sync | ✅ (Promise-based) | ✅ (`ContrastAPI`) |
164
+ | Async | (Promise model) | ✅ (`AsyncContrastAPI`) |
165
+ | Namespace count | 13 | 14 (adds `username`) |
166
+ | `bulk_technique_lookup` (ATLAS) | (added in v1.4.0) | ✅ |
167
+ | `wayback` archive lookup | (added in v1.4.0) | ✅ |
168
+ | Typed errors | `Error` subclasses | full hierarchy with envelope fields |
169
+ | Shortcuts | — | ✅ `triage_ioc`, `audit_full`, `enrich_batch` |
170
+ | Response models | `Promise<any>` | ✅ `TypedDict` (IDE autocomplete, no runtime cost) |
171
+
172
+ ## Configuration
173
+
174
+ ```python
175
+ client = ContrastAPI(
176
+ api_key="cc_...", # optional; keyless = free tier
177
+ base_url="https://api.contrastcyber.com", # override for self-host
178
+ timeout=30.0, # seconds; clamped to [1, 120]
179
+ allow_insecure=False, # set True to allow http:// (dev only)
180
+ )
181
+ ```
182
+
183
+ The transport hard-caps response bodies at 10 MB, sends a `User-Agent: contrastapi-python/<version>` header, and refuses to send your API key over plaintext HTTP even when `allow_insecure=True`.
184
+
185
+ ## Links
186
+
187
+ - API docs: https://api.contrastcyber.com/docs
188
+ - Pricing & API key: https://contrastcyber.com/pricing
189
+ - Source: https://github.com/UPinar/contrastapi
190
+ - Issues: https://github.com/UPinar/contrastapi/issues
191
+
192
+ ## License
193
+
194
+ MIT — see [LICENSE](../../LICENSE).
@@ -0,0 +1,18 @@
1
+ contrastapi/__init__.py,sha256=gqO2vZ1BJUh7p8nKHxEZk-K-DnY14ae2OUtS5vvyOUg,853
2
+ contrastapi/_transport.py,sha256=B1XylHEm3irTph0_ifUKLUq_rgHuiXwgMgb5ajaBlec,6384
3
+ contrastapi/_version.py,sha256=mumhYmLDqKimAeCXo3ds24UHb0h9ALM2B7GrhoFegAI,23
4
+ contrastapi/client.py,sha256=bpon6dHpoQ3SLCi9NmhUuVENNEeB5bfjMBhZVvYJMzo,5513
5
+ contrastapi/exceptions.py,sha256=VYsAiL1_aEzrdTiToGcO6a6VMQZ_8twbUrWwmrFyNwo,5881
6
+ contrastapi/models.py,sha256=sbu3aQ_QwYrgHmNw_G3eB9qWW0f0lwkIe6Bmiw65ANs,14892
7
+ contrastapi/shortcuts.py,sha256=edUnGiLg3xieZswX3SN1agJtNkxqbEg5sJ5MWVihX8w,6319
8
+ contrastapi/namespaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ contrastapi/namespaces/atlas.py,sha256=vsVwxwKtSuB9UZj2RkAAHhxfaGzriDclnD79tsV8AgM,4664
10
+ contrastapi/namespaces/cve.py,sha256=5-tHpVE1fsegvQ8BKrvAfVT6_BB3k1VJb0fqeoxGuZA,3841
11
+ contrastapi/namespaces/d3fend.py,sha256=qsr1sdhCbiRYxbUBiGCAyBkcU0poe3iq-miKeWpOWnw,4169
12
+ contrastapi/namespaces/domain.py,sha256=8fh6M3BPSlhApA8np3FH34fcCaY45oXne-LEDLthJOI,4611
13
+ contrastapi/namespaces/intel.py,sha256=FepgCxs2xgm1qAlpdN-3BpEyZEh54VUE4DQ7wQSEO4Q,3955
14
+ contrastapi/namespaces/ioc.py,sha256=gDgfqa5WlvpvPH7bZbCkQ81dexcQY_Ms4NiIXzfhMF4,2196
15
+ contrastapi/namespaces/security.py,sha256=iVi-oSaYbXTlr3vMXJ3k8055aGbljnGgXEtkjilM2vU,3419
16
+ contrastapi-1.22.3.dist-info/METADATA,sha256=-sOOxnwz_gj657nT2Cvltxt6Bxw7Wi-bKA9GeZyEjsc,7533
17
+ contrastapi-1.22.3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
18
+ contrastapi-1.22.3.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any