vpndetection 1.0.0__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.
Files changed (55) hide show
  1. vpndetection/__init__.py +49 -0
  2. vpndetection/_bogons.py +63 -0
  3. vpndetection/_core.py +292 -0
  4. vpndetection/_generated/__init__.py +8 -0
  5. vpndetection/_generated/api/__init__.py +1 -0
  6. vpndetection/_generated/api/database/__init__.py +1 -0
  7. vpndetection/_generated/api/database/database_checksum.py +203 -0
  8. vpndetection/_generated/api/database/database_metadata.py +205 -0
  9. vpndetection/_generated/api/database/download_database.py +201 -0
  10. vpndetection/_generated/api/database/list_databases.py +134 -0
  11. vpndetection/_generated/api/database/list_downloads.py +166 -0
  12. vpndetection/_generated/api/lookup/__init__.py +1 -0
  13. vpndetection/_generated/api/lookup/lookup_ip.py +197 -0
  14. vpndetection/_generated/client.py +272 -0
  15. vpndetection/_generated/errors.py +16 -0
  16. vpndetection/_generated/models/__init__.py +63 -0
  17. vpndetection/_generated/models/class_detail.py +91 -0
  18. vpndetection/_generated/models/database_checksum_format.py +9 -0
  19. vpndetection/_generated/models/database_checksum_response_200.py +87 -0
  20. vpndetection/_generated/models/database_checksum_response_200_checksums.py +88 -0
  21. vpndetection/_generated/models/dataset_format_size.py +77 -0
  22. vpndetection/_generated/models/dataset_format_size_format.py +9 -0
  23. vpndetection/_generated/models/dataset_metadata.py +167 -0
  24. vpndetection/_generated/models/dataset_metadata_column.py +80 -0
  25. vpndetection/_generated/models/dataset_metadata_sample.py +78 -0
  26. vpndetection/_generated/models/dataset_metadata_sample_additional_property_item.py +45 -0
  27. vpndetection/_generated/models/dataset_metadata_sample_size.py +47 -0
  28. vpndetection/_generated/models/dataset_metadata_schema.py +72 -0
  29. vpndetection/_generated/models/dataset_metadata_size.py +47 -0
  30. vpndetection/_generated/models/download.py +170 -0
  31. vpndetection/_generated/models/download_database_format.py +9 -0
  32. vpndetection/_generated/models/download_outcome.py +13 -0
  33. vpndetection/_generated/models/error.py +61 -0
  34. vpndetection/_generated/models/licensed_dataset.py +194 -0
  35. vpndetection/_generated/models/licensed_dataset_redistribution.py +10 -0
  36. vpndetection/_generated/models/licensed_dataset_standing.py +10 -0
  37. vpndetection/_generated/models/licensed_version.py +125 -0
  38. vpndetection/_generated/models/licensed_version_sample_formats_item.py +9 -0
  39. vpndetection/_generated/models/list_databases_response_200.py +75 -0
  40. vpndetection/_generated/models/list_downloads_response_200.py +75 -0
  41. vpndetection/_generated/models/lookup_error.py +62 -0
  42. vpndetection/_generated/models/lookup_response.py +295 -0
  43. vpndetection/_generated/models/proxy_detail.py +129 -0
  44. vpndetection/_generated/models/vpn_detail.py +101 -0
  45. vpndetection/_generated/types.py +54 -0
  46. vpndetection/aio.py +335 -0
  47. vpndetection/bogon.py +85 -0
  48. vpndetection/client.py +332 -0
  49. vpndetection/errors.py +119 -0
  50. vpndetection/models.py +190 -0
  51. vpndetection/py.typed +0 -0
  52. vpndetection-1.0.0.dist-info/METADATA +225 -0
  53. vpndetection-1.0.0.dist-info/RECORD +55 -0
  54. vpndetection-1.0.0.dist-info/WHEEL +4 -0
  55. vpndetection-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,49 @@
1
+ """The official Python client library for the VPNDetection API.
2
+
3
+ from vpndetection import VPNDetection
4
+
5
+ with VPNDetection() as client:
6
+ print(client.lookup("45.83.91.1").is_vpn)
7
+
8
+ No API key is needed to start. See https://vpndetection.io for the API, and the README
9
+ for batching, caching and the licensed dataset downloads.
10
+ """
11
+
12
+ from ._core import DEFAULT_BASE_URL
13
+ from ._generated.models.dataset_format_size import DatasetFormatSize
14
+ from ._generated.models.dataset_metadata import DatasetMetadata
15
+ from ._generated.models.dataset_metadata_column import DatasetMetadataColumn
16
+ from ._generated.models.download import Download
17
+ from ._generated.models.licensed_dataset import LicensedDataset
18
+ from ._generated.models.licensed_version import LicensedVersion
19
+ from .aio import AsyncDatabaseApi, AsyncVPNDetection
20
+ from .bogon import is_bogon
21
+ from .client import DatabaseApi, VPNDetection
22
+ from .errors import ErrorKind, VPNDetectionError
23
+ from .models import ClassDetail, Flag, Format, ProxyDetail, Result, VpnDetail
24
+
25
+ __version__ = "1.0.0"
26
+
27
+ __all__ = [
28
+ "DEFAULT_BASE_URL",
29
+ "AsyncDatabaseApi",
30
+ "AsyncVPNDetection",
31
+ "ClassDetail",
32
+ "DatabaseApi",
33
+ "DatasetFormatSize",
34
+ "DatasetMetadata",
35
+ "DatasetMetadataColumn",
36
+ "Download",
37
+ "ErrorKind",
38
+ "Flag",
39
+ "Format",
40
+ "LicensedDataset",
41
+ "LicensedVersion",
42
+ "ProxyDetail",
43
+ "Result",
44
+ "VPNDetection",
45
+ "VPNDetectionError",
46
+ "VpnDetail",
47
+ "__version__",
48
+ "is_bogon",
49
+ ]
@@ -0,0 +1,63 @@
1
+ # Generated by sdk/common. Do not edit.
2
+ # The canonical list lives in the monorepo and is checked against both
3
+ # upstream definitions on every regeneration.
4
+
5
+ BOGON_V4: tuple[str, ...] = (
6
+ "0.0.0.0/8",
7
+ "10.0.0.0/8",
8
+ "100.64.0.0/10",
9
+ "127.0.0.0/8",
10
+ "169.254.0.0/16",
11
+ "172.16.0.0/12",
12
+ "192.0.0.0/24",
13
+ "192.0.2.0/24",
14
+ "192.168.0.0/16",
15
+ "198.18.0.0/15",
16
+ "198.51.100.0/24",
17
+ "203.0.113.0/24",
18
+ "224.0.0.0/4",
19
+ "240.0.0.0/4",
20
+ "255.255.255.255/32",
21
+ )
22
+
23
+ BOGON_V6: tuple[str, ...] = (
24
+ "::/128",
25
+ "::1/128",
26
+ "::ffff:0:0/96",
27
+ "::/96",
28
+ "100::/64",
29
+ "2001:10::/28",
30
+ "2001:db8::/32",
31
+ "fc00::/7",
32
+ "fe80::/10",
33
+ "fec0::/10",
34
+ "ff00::/8",
35
+ "2002::/24",
36
+ "2002:a00::/24",
37
+ "2002:7f00::/24",
38
+ "2002:a9fe::/32",
39
+ "2002:ac10::/28",
40
+ "2002:c000::/40",
41
+ "2002:c000:200::/40",
42
+ "2002:c0a8::/32",
43
+ "2002:c612::/31",
44
+ "2002:c633:6400::/40",
45
+ "2002:cb00:7100::/40",
46
+ "2002:e000::/20",
47
+ "2002:f000::/20",
48
+ "2002:ffff:ffff::/48",
49
+ "2001::/40",
50
+ "2001:0:a00::/40",
51
+ "2001:0:7f00::/40",
52
+ "2001:0:a9fe::/48",
53
+ "2001:0:ac10::/44",
54
+ "2001:0:c000::/56",
55
+ "2001:0:c000:200::/56",
56
+ "2001:0:c0a8::/48",
57
+ "2001:0:c612::/47",
58
+ "2001:0:c633:6400::/56",
59
+ "2001:0:cb00:7100::/56",
60
+ "2001:0:e000::/36",
61
+ "2001:0:f000::/36",
62
+ "2001:0:ffff:ffff::/64",
63
+ )
vpndetection/_core.py ADDED
@@ -0,0 +1,292 @@
1
+ """Plumbing the sync and the async client both need: transport wiring, response
2
+ unwrapping, the retry policy, and the per-instance cache."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import contextlib
7
+ import json
8
+ import os
9
+ import threading
10
+ from collections.abc import Awaitable, Callable, Iterator
11
+ from pathlib import Path
12
+ from typing import IO, Any, TypeVar, cast
13
+
14
+ import httpx
15
+ from cachetools import TTLCache
16
+
17
+ from ._generated.client import AuthenticatedClient, Client
18
+ from ._generated.models.download import Download
19
+ from ._generated.models.licensed_dataset import LicensedDataset
20
+ from ._generated.types import Response
21
+ from .errors import VPNDetectionError, error_from_response
22
+ from .models import Result
23
+
24
+ DEFAULT_BASE_URL = "https://api.vpndetection.io"
25
+ DEFAULT_CONCURRENCY = 8
26
+ DEFAULT_RETRIES = 2
27
+ DEFAULT_CACHE_MAX_SIZE = 10_000
28
+ DEFAULT_CACHE_TTL = 3600.0
29
+ DEFAULT_TIMEOUT = 10.0
30
+ DEFAULT_DOWNLOADS_LIMIT = 50
31
+
32
+ # One chunk of a transfer, and therefore the ceiling on what a download of any size
33
+ # costs in memory.
34
+ TRANSFER_CHUNK_BYTES = 1 << 20
35
+
36
+ _BACKOFF_BASE = 1.0
37
+
38
+ T = TypeVar("T")
39
+
40
+
41
+ def build_client(
42
+ api_key: str | None,
43
+ base_url: str,
44
+ timeout: float | None,
45
+ transport: httpx.BaseTransport | httpx.AsyncBaseTransport | None,
46
+ ) -> AuthenticatedClient:
47
+ """The generated client, wired for one of our clients.
48
+
49
+ Every generated endpoint function types `client` as `AuthenticatedClient` because
50
+ every operation lists a security scheme, but the lookup endpoint also accepts `{}`
51
+ and a keyless caller must not send an empty `Authorization` header. The two classes
52
+ are interchangeable where the endpoints use them, so the keyless one is built as
53
+ `Client` and the cast lives here instead of at every call site.
54
+ """
55
+ httpx_args: dict[str, Any] = {}
56
+ if transport is not None:
57
+ httpx_args["transport"] = transport
58
+ if api_key is None:
59
+ return cast(
60
+ AuthenticatedClient,
61
+ Client(base_url=base_url, timeout=httpx.Timeout(timeout), httpx_args=httpx_args),
62
+ )
63
+ return AuthenticatedClient(
64
+ base_url=base_url,
65
+ token=api_key,
66
+ timeout=httpx.Timeout(timeout),
67
+ httpx_args=httpx_args,
68
+ )
69
+
70
+
71
+ def build_transfer_client(
72
+ timeout: float | None, transport: httpx.BaseTransport | None
73
+ ) -> httpx.Client:
74
+ """A SECOND client, holding no credential, for the object-storage leg of a download.
75
+
76
+ The API answers a download with a `302` to a presigned URL, and that URL authorizes
77
+ itself. Following the redirect on the API client would forward the key to a host
78
+ with no business holding it, so the second request is made from here instead, where
79
+ there is no `Authorization` header to send.
80
+
81
+ Only the connect phase keeps the client's timeout. That timeout is a sane bound on a
82
+ lookup and the wrong one on a body that routinely runs to gigabytes, which would
83
+ otherwise be cut off mid-transfer. Redirects ARE followed here, unlike on the API
84
+ client: object storage behind a CDN answers one, and there is no credential to leak
85
+ by going along with it.
86
+ """
87
+ return httpx.Client(
88
+ timeout=httpx.Timeout(None, connect=timeout),
89
+ transport=transport,
90
+ follow_redirects=True,
91
+ )
92
+
93
+
94
+ def build_async_transfer_client(
95
+ timeout: float | None, transport: httpx.AsyncBaseTransport | None
96
+ ) -> httpx.AsyncClient:
97
+ """`build_transfer_client`, for asyncio."""
98
+ return httpx.AsyncClient(
99
+ timeout=httpx.Timeout(None, connect=timeout),
100
+ transport=transport,
101
+ follow_redirects=True,
102
+ )
103
+
104
+
105
+ def storage_refusal(res: httpx.Response) -> VPNDetectionError:
106
+ """What object storage refusing a download link becomes.
107
+
108
+ The body is deliberately left unread: the status is what separates a lapsed link
109
+ from a refused one, and nothing bounds the size of an error page.
110
+ """
111
+ return error_from_response(
112
+ res.status_code,
113
+ res.headers,
114
+ {"error": f"object storage refused the download link with status {res.status_code}"},
115
+ )
116
+
117
+
118
+ def assert_whole_transfer(res: httpx.Response, written: int) -> None:
119
+ """Check what arrived against what was promised.
120
+
121
+ A transfer that dies mid-body can reach a client as a plain end of stream, and a
122
+ short file that looks complete is worse than no file at all: the next run reads it
123
+ as a whole dataset.
124
+
125
+ Skipped when the body was decoded on the way in, because `Content-Length` then
126
+ describes the ENCODED bytes and disagreeing with it is correct rather than short.
127
+ A chunked response declares no length; httpx raises for itself when one of those is
128
+ cut off.
129
+ """
130
+ declared = res.headers.get("content-length")
131
+ encoding = res.headers.get("content-encoding", "identity").strip().lower()
132
+ if declared is None or encoding not in ("", "identity"):
133
+ return
134
+ try:
135
+ expected = int(declared)
136
+ except ValueError:
137
+ return
138
+ if expected != written:
139
+ raise VPNDetectionError(
140
+ "network",
141
+ f"the transfer ended after {written} of {expected} bytes",
142
+ res.status_code,
143
+ )
144
+
145
+
146
+ @contextlib.contextmanager
147
+ def part_file(destination: str | os.PathLike[str]) -> Iterator[IO[bytes]]:
148
+ """A download's bytes, landing beside `destination` and moved onto it at the end.
149
+
150
+ Two failures this prevents, and only the first is the obvious one. A transfer that
151
+ dies half way leaves no truncated file carrying the real name. And a refresh that
152
+ fails leaves yesterday's good copy untouched, which opening the destination itself
153
+ could not do: that truncates it before the first byte of the new one arrives.
154
+ """
155
+ partial = os.fspath(destination) + ".part"
156
+ try:
157
+ with open(partial, "wb") as sink:
158
+ yield sink
159
+ os.replace(partial, destination)
160
+ except BaseException:
161
+ Path(partial).unlink(missing_ok=True)
162
+ raise
163
+
164
+
165
+ def send(call: Callable[[], Response[Any]]) -> Response[Any]:
166
+ """One generated endpoint call.
167
+
168
+ The generated code eagerly parses the body of every DOCUMENTED status as JSON, so a
169
+ 429 or a 400 carrying an intermediary's HTML error page raises straight out of it.
170
+ That is a failed request rather than a bug in the caller's code, so it becomes the
171
+ one error type here.
172
+ """
173
+ try:
174
+ return call()
175
+ except ValueError as exc:
176
+ raise VPNDetectionError(
177
+ "server_error", f"the API answered with a body this client could not read: {exc}"
178
+ ) from exc
179
+
180
+
181
+ async def send_async(call: Callable[[], Awaitable[Response[Any]]]) -> Response[Any]:
182
+ """`send`, awaited."""
183
+ try:
184
+ return await call()
185
+ except ValueError as exc:
186
+ raise VPNDetectionError(
187
+ "server_error", f"the API answered with a body this client could not read: {exc}"
188
+ ) from exc
189
+
190
+
191
+ def unwrap(res: Response[Any]) -> dict[str, Any]:
192
+ """The response body as it came off the wire, or the failure it describes."""
193
+ body = _decode(res.content)
194
+ status = int(res.status_code)
195
+ if not 200 <= status < 300:
196
+ raise error_from_response(status, _headers(res), body)
197
+ if not isinstance(body, dict):
198
+ raise VPNDetectionError("server_error", "the API answered with a non-object body", status)
199
+ return body
200
+
201
+
202
+ def as_error(exc: VPNDetectionError | httpx.HTTPError) -> VPNDetectionError:
203
+ """A transport failure, as the one error type this library raises.
204
+
205
+ Deliberately narrow: anything else is a bug rather than a failed request, and
206
+ turning it into a `network` error here would hide it behind a retry.
207
+ """
208
+ if isinstance(exc, VPNDetectionError):
209
+ return exc
210
+ return VPNDetectionError("network", str(exc) or type(exc).__name__)
211
+
212
+
213
+ def parse_body(body: dict[str, Any], parse: Callable[[dict[str, Any]], T]) -> T:
214
+ """A served body through its generated model, with a malformed one reported as the
215
+ server's failure rather than as a traceback out of the generated code."""
216
+ try:
217
+ return parse(body)
218
+ except (KeyError, TypeError, ValueError) as exc:
219
+ raise VPNDetectionError("server_error", f"malformed response from the API: {exc}") from exc
220
+
221
+
222
+ def redirect_location(res: Response[Any]) -> str:
223
+ """Where a `302` points, or whatever the API said instead."""
224
+ location: str | None = _headers(res).get("location")
225
+ if int(res.status_code) == 302 and location:
226
+ return location
227
+ unwrap(res)
228
+ raise VPNDetectionError(
229
+ "server_error", "expected a redirect to object storage", int(res.status_code)
230
+ )
231
+
232
+
233
+ def datasets_of(body: dict[str, Any]) -> list[LicensedDataset]:
234
+ return [LicensedDataset.from_dict(d) for d in body["datasets"]]
235
+
236
+
237
+ def downloads_of(body: dict[str, Any]) -> list[Download]:
238
+ return [Download.from_dict(d) for d in body["downloads"]]
239
+
240
+
241
+ def checksums_of(body: dict[str, Any]) -> dict[str, str]:
242
+ return dict(body["checksums"])
243
+
244
+
245
+ def retry_delay(err: VPNDetectionError, attempt: int, retries: int) -> float | None:
246
+ """How long to wait before attempt `attempt + 1`, or None when there must not be one.
247
+
248
+ A server-supplied `Retry-After` wins over the backoff schedule outright: it is the
249
+ only thing that makes a 429 worth retrying at all, so second-guessing it with a
250
+ shorter wait would just spend the next attempt on the same rejection.
251
+ """
252
+ if attempt >= retries or not err.retryable:
253
+ return None
254
+ if err.retry_after_seconds is not None:
255
+ return err.retry_after_seconds
256
+ return _BACKOFF_BASE * (2.0**attempt)
257
+
258
+
259
+ class Cache:
260
+ """A per-client result cache.
261
+
262
+ Never global or static: two clients with different keys are on different plans and
263
+ entitled to different fields, so a shared cache would serve one of them the other's
264
+ shape. The lock is not optional, because the sync batch drives lookups from a thread
265
+ pool and `TTLCache` is not thread-safe.
266
+ """
267
+
268
+ def __init__(self, max_size: int, ttl: float) -> None:
269
+ self._entries: TTLCache[str, Result] = TTLCache(maxsize=max_size, ttl=ttl)
270
+ self._lock = threading.Lock()
271
+
272
+ def get(self, ip: str) -> Result | None:
273
+ with self._lock:
274
+ return self._entries.get(ip)
275
+
276
+ def put(self, ip: str, result: Result) -> None:
277
+ with self._lock:
278
+ self._entries[ip] = result
279
+
280
+
281
+ # The generated Response declares a plain MutableMapping, but always carries httpx's
282
+ # case-insensitive Headers. Rebuilding one keeps a header lookup case-blind whichever it
283
+ # turns out to be, which matters for `Retry-After`.
284
+ def _headers(res: Response[Any]) -> httpx.Headers:
285
+ return httpx.Headers(res.headers)
286
+
287
+
288
+ def _decode(content: bytes) -> Any:
289
+ try:
290
+ return json.loads(content)
291
+ except ValueError:
292
+ return None
@@ -0,0 +1,8 @@
1
+ """A client library for accessing VPNDetection API"""
2
+
3
+ from .client import AuthenticatedClient, Client
4
+
5
+ __all__ = (
6
+ "AuthenticatedClient",
7
+ "Client",
8
+ )
@@ -0,0 +1 @@
1
+ """Contains methods for accessing the API"""
@@ -0,0 +1 @@
1
+ """Contains endpoint functions for accessing the API"""
@@ -0,0 +1,203 @@
1
+ from http import HTTPStatus
2
+ from typing import Any
3
+
4
+ import httpx
5
+
6
+ from ... import errors
7
+ from ...client import AuthenticatedClient, Client
8
+ from ...models.database_checksum_format import DatabaseChecksumFormat
9
+ from ...models.database_checksum_response_200 import DatabaseChecksumResponse200
10
+ from ...models.error import Error
11
+ from ...types import UNSET, Response
12
+
13
+
14
+ def _get_kwargs(
15
+ *,
16
+ id: str,
17
+ format_: DatabaseChecksumFormat,
18
+ ) -> dict[str, Any]:
19
+
20
+ params: dict[str, Any] = {}
21
+
22
+ params["id"] = id
23
+
24
+ json_format_ = format_.value
25
+ params["format"] = json_format_
26
+
27
+ params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
28
+
29
+ _kwargs: dict[str, Any] = {
30
+ "method": "get",
31
+ "url": "/api/v1/database/checksum",
32
+ "params": params,
33
+ }
34
+
35
+ return _kwargs
36
+
37
+
38
+ def _parse_response(
39
+ *, client: AuthenticatedClient | Client, response: httpx.Response
40
+ ) -> DatabaseChecksumResponse200 | Error | None:
41
+ if response.status_code == 200:
42
+ response_200 = DatabaseChecksumResponse200.from_dict(response.json())
43
+
44
+ return response_200
45
+
46
+ if response.status_code == 400:
47
+ response_400 = Error.from_dict(response.json())
48
+
49
+ return response_400
50
+
51
+ if response.status_code == 401:
52
+ response_401 = Error.from_dict(response.json())
53
+
54
+ return response_401
55
+
56
+ if response.status_code == 403:
57
+ response_403 = Error.from_dict(response.json())
58
+
59
+ return response_403
60
+
61
+ if response.status_code == 404:
62
+ response_404 = Error.from_dict(response.json())
63
+
64
+ return response_404
65
+
66
+ if response.status_code == 503:
67
+ response_503 = Error.from_dict(response.json())
68
+
69
+ return response_503
70
+
71
+ if client.raise_on_unexpected_status:
72
+ raise errors.UnexpectedStatus(response.status_code, response.content)
73
+ else:
74
+ return None
75
+
76
+
77
+ def _build_response(
78
+ *, client: AuthenticatedClient | Client, response: httpx.Response
79
+ ) -> Response[DatabaseChecksumResponse200 | Error]:
80
+ return Response(
81
+ status_code=HTTPStatus(response.status_code),
82
+ content=response.content,
83
+ headers=response.headers,
84
+ parsed=_parse_response(client=client, response=response),
85
+ )
86
+
87
+
88
+ def sync_detailed(
89
+ *,
90
+ client: AuthenticatedClient,
91
+ id: str,
92
+ format_: DatabaseChecksumFormat,
93
+ ) -> Response[DatabaseChecksumResponse200 | Error]:
94
+ """Checksums for one published file, to verify a download
95
+
96
+ Args:
97
+ id (str):
98
+ format_ (DatabaseChecksumFormat):
99
+
100
+ Raises:
101
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
102
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
103
+
104
+ Returns:
105
+ Response[DatabaseChecksumResponse200 | Error]
106
+ """
107
+
108
+ kwargs = _get_kwargs(
109
+ id=id,
110
+ format_=format_,
111
+ )
112
+
113
+ response = client.get_httpx_client().request(
114
+ **kwargs,
115
+ )
116
+
117
+ return _build_response(client=client, response=response)
118
+
119
+
120
+ def sync(
121
+ *,
122
+ client: AuthenticatedClient,
123
+ id: str,
124
+ format_: DatabaseChecksumFormat,
125
+ ) -> DatabaseChecksumResponse200 | Error | None:
126
+ """Checksums for one published file, to verify a download
127
+
128
+ Args:
129
+ id (str):
130
+ format_ (DatabaseChecksumFormat):
131
+
132
+ Raises:
133
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
134
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
135
+
136
+ Returns:
137
+ DatabaseChecksumResponse200 | Error
138
+ """
139
+
140
+ return sync_detailed(
141
+ client=client,
142
+ id=id,
143
+ format_=format_,
144
+ ).parsed
145
+
146
+
147
+ async def asyncio_detailed(
148
+ *,
149
+ client: AuthenticatedClient,
150
+ id: str,
151
+ format_: DatabaseChecksumFormat,
152
+ ) -> Response[DatabaseChecksumResponse200 | Error]:
153
+ """Checksums for one published file, to verify a download
154
+
155
+ Args:
156
+ id (str):
157
+ format_ (DatabaseChecksumFormat):
158
+
159
+ Raises:
160
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
161
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
162
+
163
+ Returns:
164
+ Response[DatabaseChecksumResponse200 | Error]
165
+ """
166
+
167
+ kwargs = _get_kwargs(
168
+ id=id,
169
+ format_=format_,
170
+ )
171
+
172
+ response = await client.get_async_httpx_client().request(**kwargs)
173
+
174
+ return _build_response(client=client, response=response)
175
+
176
+
177
+ async def asyncio(
178
+ *,
179
+ client: AuthenticatedClient,
180
+ id: str,
181
+ format_: DatabaseChecksumFormat,
182
+ ) -> DatabaseChecksumResponse200 | Error | None:
183
+ """Checksums for one published file, to verify a download
184
+
185
+ Args:
186
+ id (str):
187
+ format_ (DatabaseChecksumFormat):
188
+
189
+ Raises:
190
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
191
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
192
+
193
+ Returns:
194
+ DatabaseChecksumResponse200 | Error
195
+ """
196
+
197
+ return (
198
+ await asyncio_detailed(
199
+ client=client,
200
+ id=id,
201
+ format_=format_,
202
+ )
203
+ ).parsed