contrastapi 1.24.0__tar.gz → 2.0.0__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 (44) hide show
  1. contrastapi-2.0.0/.gitignore +8 -0
  2. contrastapi-2.0.0/LICENSE +21 -0
  3. contrastapi-2.0.0/PKG-INFO +233 -0
  4. contrastapi-2.0.0/README.md +203 -0
  5. contrastapi-2.0.0/contrastapi/__init__.py +32 -0
  6. contrastapi-2.0.0/contrastapi/_client.py +649 -0
  7. contrastapi-2.0.0/contrastapi/_operations.py +2208 -0
  8. contrastapi-2.0.0/contrastapi/_version.py +1 -0
  9. contrastapi-2.0.0/contrastapi/exceptions.py +131 -0
  10. {contrastapi-1.24.0 → contrastapi-2.0.0}/pyproject.toml +11 -8
  11. contrastapi-2.0.0/tests/conftest.py +157 -0
  12. contrastapi-2.0.0/tests/test_operations.py +198 -0
  13. contrastapi-2.0.0/tests/test_parity.py +580 -0
  14. contrastapi-2.0.0/tests/test_request_shape.py +283 -0
  15. contrastapi-2.0.0/tests/test_responses.py +229 -0
  16. contrastapi-2.0.0/tests/test_retry.py +294 -0
  17. contrastapi-1.24.0/.gitignore +0 -39
  18. contrastapi-1.24.0/PKG-INFO +0 -195
  19. contrastapi-1.24.0/README.md +0 -165
  20. contrastapi-1.24.0/contrastapi/__init__.py +0 -39
  21. contrastapi-1.24.0/contrastapi/_transport.py +0 -170
  22. contrastapi-1.24.0/contrastapi/_version.py +0 -1
  23. contrastapi-1.24.0/contrastapi/client.py +0 -174
  24. contrastapi-1.24.0/contrastapi/exceptions.py +0 -177
  25. contrastapi-1.24.0/contrastapi/models.py +0 -743
  26. contrastapi-1.24.0/contrastapi/namespaces/atlas.py +0 -134
  27. contrastapi-1.24.0/contrastapi/namespaces/cve.py +0 -143
  28. contrastapi-1.24.0/contrastapi/namespaces/d3fend.py +0 -124
  29. contrastapi-1.24.0/contrastapi/namespaces/domain.py +0 -159
  30. contrastapi-1.24.0/contrastapi/namespaces/intel.py +0 -166
  31. contrastapi-1.24.0/contrastapi/namespaces/ioc.py +0 -61
  32. contrastapi-1.24.0/contrastapi/namespaces/security.py +0 -101
  33. contrastapi-1.24.0/contrastapi/namespaces/sigma.py +0 -42
  34. contrastapi-1.24.0/contrastapi/shortcuts.py +0 -182
  35. contrastapi-1.24.0/tests/__init__.py +0 -0
  36. contrastapi-1.24.0/tests/conftest.py +0 -15
  37. contrastapi-1.24.0/tests/test_namespaces.py +0 -325
  38. contrastapi-1.24.0/tests/test_remaining_namespaces.py +0 -340
  39. contrastapi-1.24.0/tests/test_review_fixes.py +0 -177
  40. contrastapi-1.24.0/tests/test_review_round2_fixes.py +0 -371
  41. contrastapi-1.24.0/tests/test_shortcuts.py +0 -274
  42. contrastapi-1.24.0/tests/test_transport.py +0 -289
  43. contrastapi-1.24.0/tests/test_v1_23_endpoints.py +0 -152
  44. /contrastapi-1.24.0/contrastapi/namespaces/__init__.py → /contrastapi-2.0.0/contrastapi/py.typed +0 -0
@@ -0,0 +1,8 @@
1
+ node_modules/
2
+ dist/
3
+ *.vsix
4
+ *.mcpb
5
+ __pycache__/
6
+ .pytest_cache/
7
+ venv/
8
+ .venv/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 UPinar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,233 @@
1
+ Metadata-Version: 2.5
2
+ Name: contrastapi
3
+ Version: 2.0.0
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/quickstart
7
+ Project-URL: Repository, https://github.com/UPinar/contrastapi-clients/tree/master/sdk/python
8
+ Project-URL: Issues, https://github.com/UPinar/contrastapi-clients/issues
9
+ Author-email: UPinar <contact@contrastcyber.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai-security,api,attack,cve,cwe,d3fend,dns,domain,kev,mcp,mitre,osint,security,ssl,threat-intelligence,vulnerability,whois
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Security
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: httpx<1.0,>=0.25
26
+ Provides-Extra: test
27
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
28
+ Requires-Dist: pytest>=8; extra == 'test'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # ContrastAPI Python SDK
32
+
33
+ Official Python SDK for [ContrastAPI](https://api.contrastcyber.com), security intelligence for developers and AI agents.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install contrastapi
39
+ ```
40
+
41
+ It needs Python 3.10 or newer. The only dependency is `httpx>=0.25,<1.0`.
42
+
43
+ ## Quickstart
44
+
45
+ ```python
46
+ from contrastapi import ContrastAPI
47
+
48
+ with ContrastAPI() as client:
49
+ # A typed method for one operation of the REST API.
50
+ cve = client.cve_lookup(cve_id="CVE-2021-44228")
51
+
52
+ # The same operation by its id.
53
+ same = client.call("cve_lookup", {"cve_id": "CVE-2021-44228"})
54
+ ```
55
+
56
+ Each method takes keyword arguments named like the parameters of the REST API.
57
+ The client checks the names and the required values before it sends anything.
58
+ A call with an unknown name or a missing required value fails with `TypeError` and spends no credit.
59
+ A method returns the parsed JSON body.
60
+
61
+ The methods and their argument types are generated from the OpenAPI document of the server.
62
+ The package is typed (`py.typed`).
63
+
64
+ ### Raw requests
65
+
66
+ `request()` sends a request to any path. It uses the same key policy, errors, retries and credit reading as the methods.
67
+
68
+ ```python
69
+ health = client.request("GET", "/health")
70
+ found = client.request("GET", "/cve_search", query={"product": "apache"})
71
+ bulk = client.request("POST", "/cve_lookup_bulk", body={"cve_ids": ["CVE-2021-44228"]})
72
+ ```
73
+
74
+ `request()` sends a JSON body or none. It cannot send raw bytes.
75
+
76
+ ### Not in the SDK
77
+
78
+ `/image_inspect` takes a raw image body (`application/octet-stream`). The SDK sends JSON bodies only, so it has no method for it, and `request()` is no way around that. Call that endpoint with `httpx` or another HTTP client.
79
+
80
+ ### Async
81
+
82
+ `AsyncContrastAPI` has the same operation methods, `call()` and `request()`. Each one is a coroutine.
83
+
84
+ ```python
85
+ import asyncio
86
+ from contrastapi import AsyncContrastAPI
87
+
88
+ async def main():
89
+ async with AsyncContrastAPI() as client:
90
+ cve = await client.cve_lookup(cve_id="CVE-2021-44228")
91
+
92
+ asyncio.run(main())
93
+ ```
94
+
95
+ Close a client with `close()` (sync) or `await aclose()` (async), or use `with` / `async with`.
96
+
97
+ ## API key
98
+
99
+ ```python
100
+ client = ContrastAPI(api_key="your-api-key")
101
+ ```
102
+
103
+ Without `api_key`, the client reads the `CONTRASTAPI_API_KEY` environment variable and trims ASCII white space from both ends. An empty string sends no key.
104
+ A key may hold only printable ASCII without spaces; any other key raises `ValueError`, and the message does not repeat it.
105
+ The client keeps the key only when it will send it.
106
+
107
+ The client sends the key in the `X-API-Key` header. It sends it only to `api.contrastcyber.com`, `localhost`, `127.0.0.1` and `[::1]`. For any other host, pass `trust_host=True`.
108
+
109
+ `base_url` must use https. Plain http is accepted only for `localhost`, `127.0.0.1` and `[::1]`, written that way.
110
+ `base_url` must not hold user info, a query, a fragment, a backslash, a space or an ASCII control character.
111
+ Its path must not hold a `.` or `..` segment.
112
+ The client does not follow redirects: any 3xx answer fails the call with `APIConnectionError("redirect refused")`.
113
+ Before each request, the client checks that the URL keeps the scheme, host and port of `base_url` and stays under its path.
114
+ A URL that leaves it raises `ValueError` and sends nothing.
115
+ A request path with a `.` or `..` segment also raises `ValueError` and sends nothing.
116
+ This covers a raw path and a path value of a method, such as `cve_id=".."`.
117
+ In a raw path, a dot written as `%2e` or `%2E` counts as a dot.
118
+ A method encodes `%` in a path value, so a value of `%2e` is sent.
119
+ The query is not checked.
120
+ The key is not shown in `repr()` of the client.
121
+
122
+ Get a key at [api.contrastcyber.com/pricing](https://api.contrastcyber.com/pricing).
123
+ Without a key, each client address has a smaller hourly credit budget than a key has. The pricing page lists both limits.
124
+
125
+ ## Credits
126
+
127
+ Each operation has a price in credits, and some operations are free. The docstring of an operation method states its price.
128
+
129
+ After each response that carries the rate-limit headers, the client keeps the reading:
130
+
131
+ ```python
132
+ client.cve_lookup(cve_id="CVE-2021-44228")
133
+ info = client.last_rate_limit
134
+ if info is not None:
135
+ print(info.remaining, info.reset_human)
136
+
137
+ watched = ContrastAPI(on_rate_limit=lambda info: print(info.remaining, "of", info.limit, "credits left"))
138
+ ```
139
+
140
+ `RateLimitInfo` is a frozen dataclass with `limit`, `remaining`, `reset_at` (a UTC `datetime`) and `reset_human`.
141
+ `last_rate_limit` is `None` until a response has carried the headers.
142
+ An exception that `on_rate_limit` raises is ignored.
143
+
144
+ ## Errors
145
+
146
+ Every error extends `ContrastAPIError`. The server answers an error as `{"error": {"code", "message"}}`, and the client reads both.
147
+
148
+ | Class | When |
149
+ | --- | --- |
150
+ | `BadRequestError` | HTTP 400 |
151
+ | `AuthenticationError` | HTTP 401 |
152
+ | `NotFoundError` | HTTP 404 |
153
+ | `RateLimitError` | HTTP 429 |
154
+ | `UpstreamError` | HTTP 502, 503 or 504 |
155
+ | `ContrastAPIError` | any other status outside 200-399; a 2xx body that is not a UTF-8 JSON object or array; a body over 16 MiB; an answer whose `Content-Encoding` is not `identity` |
156
+ | `APIConnectionError` | no usable answer: network failure or refused redirect (`status` is `None`) |
157
+ | `APITimeoutError` | `ContrastAPI`: a read ran past `timeout`, or the body was still arriving `timeout` seconds after the request started. `AsyncContrastAPI`: the whole exchange ran past `timeout`. It extends `APIConnectionError` |
158
+
159
+ An error has these attributes: `message`, `status`, `code`, `retry_after` (seconds from the `Retry-After` header, at most 3600, or `None`) and `rate_limit` (a `RateLimitInfo` or `None`).
160
+ The message, the code and `rate_limit.reset_human` are stripped of control characters (C0, DEL and C1), format characters (Unicode Cf, such as U+202E) and lone surrogates. The message is cut to 300 characters.
161
+ An error holds neither the request nor the response, and its text never repeats the key.
162
+
163
+ ```python
164
+ from contrastapi import NotFoundError, RateLimitError
165
+
166
+ try:
167
+ client.cve_lookup(cve_id="CVE-9999-0000")
168
+ except NotFoundError as err:
169
+ print(err.status, err.code, err.message)
170
+ except RateLimitError as err:
171
+ print("retry after", err.retry_after)
172
+ ```
173
+
174
+ A wrong argument raises `TypeError` (wrong type) or `ValueError` (wrong value) before any request.
175
+ A path or query value must be a `str`, an `int`, a finite `float` or a `bool`; a float is written as JavaScript writes it (`7.0` is `7`). A query value of `None` is left out.
176
+ A body is written with `json.dumps(allow_nan=False)` before any request. A NaN or an infinity raises `ValueError`.
177
+ A value that JSON has no form for raises `TypeError`. Examples are a `datetime`, a `set`, `bytes` and a `Decimal`; write a `datetime` as an ISO string yourself.
178
+ A tuple is written as a list.
179
+ A body argument of `None` is left out, like a query value. A `None` nested inside a body value is written as `null`.
180
+
181
+ ## Retries
182
+
183
+ | Option | Default | Meaning |
184
+ | --- | --- | --- |
185
+ | `upstream_busy_retries` | 2 | retries after a 503 with the code `upstream_busy` or `raw_body_busy` |
186
+ | `max_retries` | 0 | retries after a 429; off unless you set it |
187
+ | `max_retry_wait` | 60.0 | a finite number >= 0; the client does not retry when the wait is longer than this many seconds |
188
+
189
+ The wait is the `Retry-After` header, in seconds. A busy 503 without the header waits one second. A 429 without the header is not retried.
190
+
191
+ ## Configuration
192
+
193
+ ```python
194
+ client = ContrastAPI(
195
+ api_key="...", # default: CONTRASTAPI_API_KEY
196
+ base_url="https://api.contrastcyber.com", # default
197
+ trust_host=False, # True sends the key to other hosts
198
+ timeout=30.0, # seconds; default; clamped to 1.0-120.0
199
+ max_retries=0,
200
+ upstream_busy_retries=2,
201
+ max_retry_wait=60.0,
202
+ on_rate_limit=None,
203
+ )
204
+ ```
205
+
206
+ The client sends `User-Agent: contrastapi-python/<version>`.
207
+
208
+ The client sends `Accept-Encoding: identity`. It refuses an answer with any other `Content-Encoding` and does not read its body.
209
+ It reads the raw bytes, so httpx decodes nothing.
210
+
211
+ In `ContrastAPI`, `timeout` bounds each read, not the header phase as a whole. A server that sends its headers slowly, each read within `timeout`, can hold a call longer.
212
+ `AsyncContrastAPI` bounds the whole exchange by `timeout`, the headers included.
213
+
214
+ The client uses httpx's default `trust_env=True`, so proxy environment variables (`HTTPS_PROXY` and the like) apply.
215
+
216
+ ## Regenerating the methods
217
+
218
+ The methods come from `sdk/openapi.json`, the output of `make -s openapi` in the server repository.
219
+ After a new document, run this from the root of the repository that holds `sdk/`:
220
+
221
+ ```bash
222
+ python3 sdk/gen/gen_sdk.py # rewrite the generated files
223
+ python3 sdk/gen/gen_sdk.py --check # report a difference, exit 1
224
+ ```
225
+
226
+ ## Upgrading from 1.x
227
+
228
+ Version 1.x called `/v1/...` paths. The OpenAPI document of the current server has no such path.
229
+ Version 2.0.0 is a rewrite. The [CHANGELOG](https://github.com/UPinar/contrastapi-clients/blob/master/sdk/python/CHANGELOG.md) in the source repository, [UPinar/contrastapi-clients](https://github.com/UPinar/contrastapi-clients), lists the breaking changes.
230
+
231
+ ## License
232
+
233
+ MIT. The LICENSE file is part of the package.
@@ -0,0 +1,203 @@
1
+ # ContrastAPI Python SDK
2
+
3
+ Official Python SDK for [ContrastAPI](https://api.contrastcyber.com), security intelligence for developers and AI agents.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install contrastapi
9
+ ```
10
+
11
+ It needs Python 3.10 or newer. The only dependency is `httpx>=0.25,<1.0`.
12
+
13
+ ## Quickstart
14
+
15
+ ```python
16
+ from contrastapi import ContrastAPI
17
+
18
+ with ContrastAPI() as client:
19
+ # A typed method for one operation of the REST API.
20
+ cve = client.cve_lookup(cve_id="CVE-2021-44228")
21
+
22
+ # The same operation by its id.
23
+ same = client.call("cve_lookup", {"cve_id": "CVE-2021-44228"})
24
+ ```
25
+
26
+ Each method takes keyword arguments named like the parameters of the REST API.
27
+ The client checks the names and the required values before it sends anything.
28
+ A call with an unknown name or a missing required value fails with `TypeError` and spends no credit.
29
+ A method returns the parsed JSON body.
30
+
31
+ The methods and their argument types are generated from the OpenAPI document of the server.
32
+ The package is typed (`py.typed`).
33
+
34
+ ### Raw requests
35
+
36
+ `request()` sends a request to any path. It uses the same key policy, errors, retries and credit reading as the methods.
37
+
38
+ ```python
39
+ health = client.request("GET", "/health")
40
+ found = client.request("GET", "/cve_search", query={"product": "apache"})
41
+ bulk = client.request("POST", "/cve_lookup_bulk", body={"cve_ids": ["CVE-2021-44228"]})
42
+ ```
43
+
44
+ `request()` sends a JSON body or none. It cannot send raw bytes.
45
+
46
+ ### Not in the SDK
47
+
48
+ `/image_inspect` takes a raw image body (`application/octet-stream`). The SDK sends JSON bodies only, so it has no method for it, and `request()` is no way around that. Call that endpoint with `httpx` or another HTTP client.
49
+
50
+ ### Async
51
+
52
+ `AsyncContrastAPI` has the same operation methods, `call()` and `request()`. Each one is a coroutine.
53
+
54
+ ```python
55
+ import asyncio
56
+ from contrastapi import AsyncContrastAPI
57
+
58
+ async def main():
59
+ async with AsyncContrastAPI() as client:
60
+ cve = await client.cve_lookup(cve_id="CVE-2021-44228")
61
+
62
+ asyncio.run(main())
63
+ ```
64
+
65
+ Close a client with `close()` (sync) or `await aclose()` (async), or use `with` / `async with`.
66
+
67
+ ## API key
68
+
69
+ ```python
70
+ client = ContrastAPI(api_key="your-api-key")
71
+ ```
72
+
73
+ Without `api_key`, the client reads the `CONTRASTAPI_API_KEY` environment variable and trims ASCII white space from both ends. An empty string sends no key.
74
+ A key may hold only printable ASCII without spaces; any other key raises `ValueError`, and the message does not repeat it.
75
+ The client keeps the key only when it will send it.
76
+
77
+ The client sends the key in the `X-API-Key` header. It sends it only to `api.contrastcyber.com`, `localhost`, `127.0.0.1` and `[::1]`. For any other host, pass `trust_host=True`.
78
+
79
+ `base_url` must use https. Plain http is accepted only for `localhost`, `127.0.0.1` and `[::1]`, written that way.
80
+ `base_url` must not hold user info, a query, a fragment, a backslash, a space or an ASCII control character.
81
+ Its path must not hold a `.` or `..` segment.
82
+ The client does not follow redirects: any 3xx answer fails the call with `APIConnectionError("redirect refused")`.
83
+ Before each request, the client checks that the URL keeps the scheme, host and port of `base_url` and stays under its path.
84
+ A URL that leaves it raises `ValueError` and sends nothing.
85
+ A request path with a `.` or `..` segment also raises `ValueError` and sends nothing.
86
+ This covers a raw path and a path value of a method, such as `cve_id=".."`.
87
+ In a raw path, a dot written as `%2e` or `%2E` counts as a dot.
88
+ A method encodes `%` in a path value, so a value of `%2e` is sent.
89
+ The query is not checked.
90
+ The key is not shown in `repr()` of the client.
91
+
92
+ Get a key at [api.contrastcyber.com/pricing](https://api.contrastcyber.com/pricing).
93
+ Without a key, each client address has a smaller hourly credit budget than a key has. The pricing page lists both limits.
94
+
95
+ ## Credits
96
+
97
+ Each operation has a price in credits, and some operations are free. The docstring of an operation method states its price.
98
+
99
+ After each response that carries the rate-limit headers, the client keeps the reading:
100
+
101
+ ```python
102
+ client.cve_lookup(cve_id="CVE-2021-44228")
103
+ info = client.last_rate_limit
104
+ if info is not None:
105
+ print(info.remaining, info.reset_human)
106
+
107
+ watched = ContrastAPI(on_rate_limit=lambda info: print(info.remaining, "of", info.limit, "credits left"))
108
+ ```
109
+
110
+ `RateLimitInfo` is a frozen dataclass with `limit`, `remaining`, `reset_at` (a UTC `datetime`) and `reset_human`.
111
+ `last_rate_limit` is `None` until a response has carried the headers.
112
+ An exception that `on_rate_limit` raises is ignored.
113
+
114
+ ## Errors
115
+
116
+ Every error extends `ContrastAPIError`. The server answers an error as `{"error": {"code", "message"}}`, and the client reads both.
117
+
118
+ | Class | When |
119
+ | --- | --- |
120
+ | `BadRequestError` | HTTP 400 |
121
+ | `AuthenticationError` | HTTP 401 |
122
+ | `NotFoundError` | HTTP 404 |
123
+ | `RateLimitError` | HTTP 429 |
124
+ | `UpstreamError` | HTTP 502, 503 or 504 |
125
+ | `ContrastAPIError` | any other status outside 200-399; a 2xx body that is not a UTF-8 JSON object or array; a body over 16 MiB; an answer whose `Content-Encoding` is not `identity` |
126
+ | `APIConnectionError` | no usable answer: network failure or refused redirect (`status` is `None`) |
127
+ | `APITimeoutError` | `ContrastAPI`: a read ran past `timeout`, or the body was still arriving `timeout` seconds after the request started. `AsyncContrastAPI`: the whole exchange ran past `timeout`. It extends `APIConnectionError` |
128
+
129
+ An error has these attributes: `message`, `status`, `code`, `retry_after` (seconds from the `Retry-After` header, at most 3600, or `None`) and `rate_limit` (a `RateLimitInfo` or `None`).
130
+ The message, the code and `rate_limit.reset_human` are stripped of control characters (C0, DEL and C1), format characters (Unicode Cf, such as U+202E) and lone surrogates. The message is cut to 300 characters.
131
+ An error holds neither the request nor the response, and its text never repeats the key.
132
+
133
+ ```python
134
+ from contrastapi import NotFoundError, RateLimitError
135
+
136
+ try:
137
+ client.cve_lookup(cve_id="CVE-9999-0000")
138
+ except NotFoundError as err:
139
+ print(err.status, err.code, err.message)
140
+ except RateLimitError as err:
141
+ print("retry after", err.retry_after)
142
+ ```
143
+
144
+ A wrong argument raises `TypeError` (wrong type) or `ValueError` (wrong value) before any request.
145
+ A path or query value must be a `str`, an `int`, a finite `float` or a `bool`; a float is written as JavaScript writes it (`7.0` is `7`). A query value of `None` is left out.
146
+ A body is written with `json.dumps(allow_nan=False)` before any request. A NaN or an infinity raises `ValueError`.
147
+ A value that JSON has no form for raises `TypeError`. Examples are a `datetime`, a `set`, `bytes` and a `Decimal`; write a `datetime` as an ISO string yourself.
148
+ A tuple is written as a list.
149
+ A body argument of `None` is left out, like a query value. A `None` nested inside a body value is written as `null`.
150
+
151
+ ## Retries
152
+
153
+ | Option | Default | Meaning |
154
+ | --- | --- | --- |
155
+ | `upstream_busy_retries` | 2 | retries after a 503 with the code `upstream_busy` or `raw_body_busy` |
156
+ | `max_retries` | 0 | retries after a 429; off unless you set it |
157
+ | `max_retry_wait` | 60.0 | a finite number >= 0; the client does not retry when the wait is longer than this many seconds |
158
+
159
+ The wait is the `Retry-After` header, in seconds. A busy 503 without the header waits one second. A 429 without the header is not retried.
160
+
161
+ ## Configuration
162
+
163
+ ```python
164
+ client = ContrastAPI(
165
+ api_key="...", # default: CONTRASTAPI_API_KEY
166
+ base_url="https://api.contrastcyber.com", # default
167
+ trust_host=False, # True sends the key to other hosts
168
+ timeout=30.0, # seconds; default; clamped to 1.0-120.0
169
+ max_retries=0,
170
+ upstream_busy_retries=2,
171
+ max_retry_wait=60.0,
172
+ on_rate_limit=None,
173
+ )
174
+ ```
175
+
176
+ The client sends `User-Agent: contrastapi-python/<version>`.
177
+
178
+ The client sends `Accept-Encoding: identity`. It refuses an answer with any other `Content-Encoding` and does not read its body.
179
+ It reads the raw bytes, so httpx decodes nothing.
180
+
181
+ In `ContrastAPI`, `timeout` bounds each read, not the header phase as a whole. A server that sends its headers slowly, each read within `timeout`, can hold a call longer.
182
+ `AsyncContrastAPI` bounds the whole exchange by `timeout`, the headers included.
183
+
184
+ The client uses httpx's default `trust_env=True`, so proxy environment variables (`HTTPS_PROXY` and the like) apply.
185
+
186
+ ## Regenerating the methods
187
+
188
+ The methods come from `sdk/openapi.json`, the output of `make -s openapi` in the server repository.
189
+ After a new document, run this from the root of the repository that holds `sdk/`:
190
+
191
+ ```bash
192
+ python3 sdk/gen/gen_sdk.py # rewrite the generated files
193
+ python3 sdk/gen/gen_sdk.py --check # report a difference, exit 1
194
+ ```
195
+
196
+ ## Upgrading from 1.x
197
+
198
+ Version 1.x called `/v1/...` paths. The OpenAPI document of the current server has no such path.
199
+ Version 2.0.0 is a rewrite. The [CHANGELOG](https://github.com/UPinar/contrastapi-clients/blob/master/sdk/python/CHANGELOG.md) in the source repository, [UPinar/contrastapi-clients](https://github.com/UPinar/contrastapi-clients), lists the breaking changes.
200
+
201
+ ## License
202
+
203
+ MIT. The LICENSE file is part of the package.
@@ -0,0 +1,32 @@
1
+ """Official Python SDK for ContrastAPI."""
2
+
3
+ from ._client import AsyncContrastAPI, ContrastAPI, RateLimitInfo
4
+ from ._operations import PackageItem, PackageItemOptionalVersion
5
+ from ._version import __version__
6
+ from .exceptions import (
7
+ APIConnectionError,
8
+ APITimeoutError,
9
+ AuthenticationError,
10
+ BadRequestError,
11
+ ContrastAPIError,
12
+ NotFoundError,
13
+ RateLimitError,
14
+ UpstreamError,
15
+ )
16
+
17
+ __all__ = [
18
+ "APIConnectionError",
19
+ "APITimeoutError",
20
+ "AsyncContrastAPI",
21
+ "AuthenticationError",
22
+ "BadRequestError",
23
+ "ContrastAPI",
24
+ "ContrastAPIError",
25
+ "NotFoundError",
26
+ "PackageItem",
27
+ "PackageItemOptionalVersion",
28
+ "RateLimitError",
29
+ "RateLimitInfo",
30
+ "UpstreamError",
31
+ "__version__",
32
+ ]