internetdata 2.7.3__tar.gz → 2.7.5__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.
- {internetdata-2.7.3 → internetdata-2.7.5}/PKG-INFO +1 -1
- {internetdata-2.7.3 → internetdata-2.7.5}/spec/openapi.yaml +4 -4
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/__init__.py +1 -1
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_core.py +13 -7
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/organization/account_org_members.py +4 -4
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/account_create_apikey_request.py +1 -1
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/apikey_detail.py +1 -1
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_metadata.py +1 -3
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/models.py +52 -28
- {internetdata-2.7.3 → internetdata-2.7.5}/tests/test_client.py +44 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/tests/test_conformance.py +36 -4
- {internetdata-2.7.3 → internetdata-2.7.5}/.gitignore +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/LICENSE +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/README.md +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/pyproject.toml +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/scripts/download-spec.sh +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/scripts/generate.sh +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/scripts/publish.sh +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/scripts/v2_subset.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/__init__.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/__init__.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/account/__init__.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/account/account_identity.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/api_keys/__init__.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/api_keys/account_create_apikey.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/api_keys/account_list_apikeys.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/api_keys/account_reveal_apikey.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/api_keys/account_revoke_apikey.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/api_keys/account_rotate_apikey.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/authorization/__init__.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/authorization/oauth_authorize.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/authorization/oauth_device_authorization.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/authorization/oauth_metadata.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/authorization/oauth_revoke.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/authorization/oauth_token.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/database_v_2/__init__.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/database_v_2/database_checksum_v2.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/database_v_2/database_metadata_v2.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/database_v_2/download_database_v2.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/database_v_2/list_databases.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/database_v_2/list_downloads.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/organization/__init__.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/organization/account_org.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/client.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/errors.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/__init__.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/account_created_apikey.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/account_org.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/account_org_ref.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/account_org_wrap.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/account_rc.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/account_revealed_apikey.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/account_user.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/apikey_list.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_checksum_v2_response_200.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_format.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_license_type_type_1.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_license_type_type_2_type_1.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_license_type_type_3_type_1.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_metadata_column.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_metadata_sample.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_metadata_sample_additional_property_item.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_metadata_sample_size.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_metadata_schema.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_metadata_size.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_version.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/db_checksums.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/device_authorization.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/device_authorization_request.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/download.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/download_outcome.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/error.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/identity.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/list_databases_response_200.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/list_downloads_response_200.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/oauth_authorize_code_challenge_method.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/oauth_authorize_response_type.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/oauth_error.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/oauth_metadata.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/oauth_revoke_response_200.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/revoke_request.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/standing.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/token_request.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/token_response.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/types.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/aio.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/client.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/errors.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/py.typed +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/testdata/testdata.json +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/tests/conftest.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/tests/helpers.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/tests/test_download.py +0 -0
- {internetdata-2.7.3 → internetdata-2.7.5}/tests/test_oauth.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: internetdata
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.5
|
|
4
4
|
Summary: Official Python client library for the InternetData API. Download and verify licensed IP datasets.
|
|
5
5
|
Project-URL: Homepage, https://internetdata.io
|
|
6
6
|
Project-URL: Documentation, https://docs.internetdata.io
|
|
@@ -12,7 +12,7 @@ info:
|
|
|
12
12
|
email: support@internetdata.io
|
|
13
13
|
url: https://internetdata.io
|
|
14
14
|
termsOfService: https://internetdata.io/terms
|
|
15
|
-
version: 2026.10.
|
|
15
|
+
version: 2026.10.15
|
|
16
16
|
servers:
|
|
17
17
|
- url: https://internetdata.io
|
|
18
18
|
tags:
|
|
@@ -416,7 +416,7 @@ paths:
|
|
|
416
416
|
summary: Members
|
|
417
417
|
description: |
|
|
418
418
|
Read-only. Adding or removing a member is an invitation flow with email
|
|
419
|
-
in the middle rather than a POST, and
|
|
419
|
+
in the middle rather than a POST, and modeling it as one here would
|
|
420
420
|
promise something this API does not do.
|
|
421
421
|
security:
|
|
422
422
|
- BearerAuth:
|
|
@@ -1365,7 +1365,7 @@ components:
|
|
|
1365
1365
|
type: string
|
|
1366
1366
|
key_prefix:
|
|
1367
1367
|
type: string
|
|
1368
|
-
description: The leading, non-secret part, so a key is
|
|
1368
|
+
description: The leading, non-secret part, so a key is recognizable without storing it.
|
|
1369
1369
|
created:
|
|
1370
1370
|
type: string
|
|
1371
1371
|
format: date-time
|
|
@@ -1405,7 +1405,7 @@ components:
|
|
|
1405
1405
|
properties:
|
|
1406
1406
|
name:
|
|
1407
1407
|
type: string
|
|
1408
|
-
description: A label you will
|
|
1408
|
+
description: A label you will recognize later. Shown wherever the key is listed.
|
|
1409
1409
|
allowed_scopes:
|
|
1410
1410
|
type: array
|
|
1411
1411
|
items:
|
|
@@ -81,6 +81,11 @@ def build_client(
|
|
|
81
81
|
the `Authorization` header when it CONSTRUCTS the httpx client itself.
|
|
82
82
|
"""
|
|
83
83
|
api_key = check_api_key(api_key)
|
|
84
|
+
# httpx drops one trailing slash when it joins a path onto the base URL and keeps the
|
|
85
|
+
# rest, so a second or third reached the API as `//api/v2/database/list`, which prod
|
|
86
|
+
# answers with a redirect this client does not follow: every call failed (2.7.3,
|
|
87
|
+
# measured 2026-10-16).
|
|
88
|
+
base_url = base_url.rstrip("/")
|
|
84
89
|
httpx_args: dict[str, Any] = {}
|
|
85
90
|
if transport is not None:
|
|
86
91
|
httpx_args["transport"] = transport
|
|
@@ -135,19 +140,20 @@ def build_async_transfer_client(
|
|
|
135
140
|
def check_api_key(api_key: str | None) -> str | None:
|
|
136
141
|
"""`api_key` without the blanks around it, or None when nothing else is left.
|
|
137
142
|
|
|
138
|
-
Refused where it is SET when a control character remains
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
Refused where it is SET when a control character remains, or a character outside
|
|
144
|
+
ASCII, which httpx cannot put in a header: a CR, LF or NUL failed every call as a
|
|
145
|
+
retried `network` error with nothing sent, a C1 control or any other non-ASCII
|
|
146
|
+
character as a raw `UnicodeEncodeError` (2.7.3, measured 2026-10-16), while a tab or
|
|
147
|
+
DEL went out on the wire. Untrimmed, a key of blanks was sent as one rather than as no
|
|
148
|
+
key, and failed every call the same way (2.7.1, measured 2026-10-12).
|
|
143
149
|
"""
|
|
144
150
|
if api_key is None:
|
|
145
151
|
return None
|
|
146
152
|
if not isinstance(api_key, str):
|
|
147
153
|
raise TypeError(f"api_key must be a string or None, not {type(api_key).__name__}")
|
|
148
154
|
key = api_key.strip()
|
|
149
|
-
if any(ord(char) < 0x20 or ord(char)
|
|
150
|
-
raise ValueError("api_key holds a control character")
|
|
155
|
+
if any(ord(char) < 0x20 or ord(char) >= 0x7F for char in key):
|
|
156
|
+
raise ValueError("api_key holds a control character or one outside ASCII")
|
|
151
157
|
return key or None
|
|
152
158
|
|
|
153
159
|
|
|
@@ -61,7 +61,7 @@ def sync_detailed(
|
|
|
61
61
|
"""Members
|
|
62
62
|
|
|
63
63
|
Read-only. Adding or removing a member is an invitation flow with email
|
|
64
|
-
in the middle rather than a POST, and
|
|
64
|
+
in the middle rather than a POST, and modeling it as one here would
|
|
65
65
|
promise something this API does not do.
|
|
66
66
|
|
|
67
67
|
Raises:
|
|
@@ -88,7 +88,7 @@ def sync(
|
|
|
88
88
|
"""Members
|
|
89
89
|
|
|
90
90
|
Read-only. Adding or removing a member is an invitation flow with email
|
|
91
|
-
in the middle rather than a POST, and
|
|
91
|
+
in the middle rather than a POST, and modeling it as one here would
|
|
92
92
|
promise something this API does not do.
|
|
93
93
|
|
|
94
94
|
Raises:
|
|
@@ -111,7 +111,7 @@ async def asyncio_detailed(
|
|
|
111
111
|
"""Members
|
|
112
112
|
|
|
113
113
|
Read-only. Adding or removing a member is an invitation flow with email
|
|
114
|
-
in the middle rather than a POST, and
|
|
114
|
+
in the middle rather than a POST, and modeling it as one here would
|
|
115
115
|
promise something this API does not do.
|
|
116
116
|
|
|
117
117
|
Raises:
|
|
@@ -136,7 +136,7 @@ async def asyncio(
|
|
|
136
136
|
"""Members
|
|
137
137
|
|
|
138
138
|
Read-only. Adding or removing a member is an invitation flow with email
|
|
139
|
-
in the middle rather than a POST, and
|
|
139
|
+
in the middle rather than a POST, and modeling it as one here would
|
|
140
140
|
promise something this API does not do.
|
|
141
141
|
|
|
142
142
|
Raises:
|
|
@@ -15,7 +15,7 @@ T = TypeVar("T", bound="AccountCreateApikeyRequest")
|
|
|
15
15
|
class AccountCreateApikeyRequest:
|
|
16
16
|
"""
|
|
17
17
|
Attributes:
|
|
18
|
-
name (str): A label you will
|
|
18
|
+
name (str): A label you will recognize later. Shown wherever the key is listed.
|
|
19
19
|
allowed_scopes (list[str] | Unset): What the new key may do. Omit for a key that carries no named scope, which
|
|
20
20
|
is the safe default.
|
|
21
21
|
"""
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/apikey_detail.py
RENAMED
|
@@ -20,7 +20,7 @@ class ApikeyDetail:
|
|
|
20
20
|
Attributes:
|
|
21
21
|
id (UUID):
|
|
22
22
|
name (str):
|
|
23
|
-
key_prefix (str): The leading, non-secret part, so a key is
|
|
23
|
+
key_prefix (str): The leading, non-secret part, so a key is recognizable without storing it.
|
|
24
24
|
created (datetime.datetime):
|
|
25
25
|
expires (datetime.datetime | None | Unset):
|
|
26
26
|
last_used_at (datetime.datetime | None | Unset):
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_metadata.py
RENAMED
|
@@ -95,9 +95,7 @@ class DatabaseMetadata:
|
|
|
95
95
|
@classmethod
|
|
96
96
|
def from_dict(cls, src_dict: Mapping[str, Any]) -> Self:
|
|
97
97
|
from ..models.database_metadata_sample import DatabaseMetadataSample
|
|
98
|
-
from ..models.database_metadata_sample_size import
|
|
99
|
-
DatabaseMetadataSampleSize,
|
|
100
|
-
)
|
|
98
|
+
from ..models.database_metadata_sample_size import DatabaseMetadataSampleSize
|
|
101
99
|
from ..models.database_metadata_schema import DatabaseMetadataSchema
|
|
102
100
|
from ..models.database_metadata_size import DatabaseMetadataSize
|
|
103
101
|
|
|
@@ -174,85 +174,109 @@ class Download:
|
|
|
174
174
|
|
|
175
175
|
def to_database(body: dict[str, Any]) -> Database:
|
|
176
176
|
return Database(
|
|
177
|
-
base=_present(body, "base"),
|
|
178
|
-
name=_present(body, "name"),
|
|
179
|
-
summary=_present(body, "summary"),
|
|
180
|
-
standing=_present(body, "standing"),
|
|
181
|
-
license_type=body
|
|
177
|
+
base=_present(body, "base", str),
|
|
178
|
+
name=_present(body, "name", str),
|
|
179
|
+
summary=_present(body, "summary", str),
|
|
180
|
+
standing=_present(body, "standing", str),
|
|
181
|
+
license_type=_nullable(body, "license_type", str),
|
|
182
182
|
starts=_datetime(body["starts"]),
|
|
183
183
|
expires=_datetime(body["expires"]),
|
|
184
184
|
renews_at=_datetime(body["renews_at"]),
|
|
185
185
|
notice_due_at=_datetime(body["notice_due_at"]),
|
|
186
|
-
versions=tuple(to_version(v) for v in _present(body, "versions")),
|
|
186
|
+
versions=tuple(to_version(v) for v in _present(body, "versions", list)),
|
|
187
187
|
raw=body,
|
|
188
|
-
open=_present(body, "open"),
|
|
188
|
+
open=_present(body, "open", bool),
|
|
189
189
|
)
|
|
190
190
|
|
|
191
191
|
|
|
192
192
|
def to_version(body: dict[str, Any]) -> DatabaseVersion:
|
|
193
193
|
return DatabaseVersion(
|
|
194
|
-
id=_present(body, "id"),
|
|
195
|
-
version=_present(body, "version"),
|
|
196
|
-
summary=_present(body, "summary"),
|
|
197
|
-
formats=
|
|
194
|
+
id=_present(body, "id", str),
|
|
195
|
+
version=_present(body, "version", int),
|
|
196
|
+
summary=_present(body, "summary", str),
|
|
197
|
+
formats=_strings(_present(body, "formats", list), "formats"),
|
|
198
198
|
sample_formats=_optional_tuple(body.get("sample_formats")),
|
|
199
199
|
)
|
|
200
200
|
|
|
201
201
|
|
|
202
202
|
def to_metadata(body: dict[str, Any]) -> DatabaseMetadata:
|
|
203
203
|
return DatabaseMetadata(
|
|
204
|
-
id=_present(body, "id"),
|
|
204
|
+
id=_present(body, "id", str),
|
|
205
205
|
updated=datetime.date.fromisoformat(body["updated"]),
|
|
206
|
-
entries=_present(body, "entries"),
|
|
206
|
+
entries=_present(body, "entries", int),
|
|
207
207
|
schema={
|
|
208
208
|
fmt: tuple(to_column(c) for c in columns) for fmt, columns in body["schema"].items()
|
|
209
209
|
},
|
|
210
|
-
size=
|
|
210
|
+
size=_sizes(_present(body, "size", dict), "size"),
|
|
211
211
|
update_freq=body.get("update_freq"),
|
|
212
212
|
sample={fmt: tuple(rows) for fmt, rows in body.get("sample", {}).items()},
|
|
213
213
|
raw=body,
|
|
214
|
-
sample_size=
|
|
215
|
-
|
|
214
|
+
sample_size=_sizes(body["sample_size"], "sample_size")
|
|
215
|
+
if body.get("sample_size") is not None
|
|
216
|
+
else None,
|
|
217
|
+
sample_entries=_nullable(body, "sample_entries", int, absent=True),
|
|
216
218
|
)
|
|
217
219
|
|
|
218
220
|
|
|
219
221
|
def to_column(body: dict[str, Any]) -> MetadataColumn:
|
|
220
222
|
return MetadataColumn(
|
|
221
|
-
name=_present(body, "name"),
|
|
222
|
-
type=_present(body, "type"),
|
|
223
|
+
name=_present(body, "name", str),
|
|
224
|
+
type=_present(body, "type", str),
|
|
223
225
|
description=body.get("description"),
|
|
224
226
|
)
|
|
225
227
|
|
|
226
228
|
|
|
227
229
|
def to_download(body: dict[str, Any]) -> Download:
|
|
228
230
|
return Download(
|
|
229
|
-
dataset_id=_present(body, "dataset_id"),
|
|
230
|
-
format=_present(body, "format"),
|
|
231
|
-
outcome=_present(body, "outcome"),
|
|
231
|
+
dataset_id=_present(body, "dataset_id", str),
|
|
232
|
+
format=_present(body, "format", str),
|
|
233
|
+
outcome=_present(body, "outcome", str),
|
|
232
234
|
created=datetime.datetime.fromisoformat(body["created"]),
|
|
233
|
-
bytes=body
|
|
234
|
-
http_status=body
|
|
235
|
+
bytes=_nullable(body, "bytes", int),
|
|
236
|
+
http_status=_nullable(body, "http_status", int),
|
|
235
237
|
apikey_id=body["apikey_id"],
|
|
236
238
|
client_ip=body["client_ip"],
|
|
237
239
|
user_agent=body["user_agent"],
|
|
238
|
-
sample=_present(body, "sample"),
|
|
239
|
-
open=_present(body, "open"),
|
|
240
|
+
sample=_present(body, "sample", bool),
|
|
241
|
+
open=_present(body, "open", bool),
|
|
240
242
|
)
|
|
241
243
|
|
|
242
244
|
|
|
243
|
-
def _present(body: dict[str, Any], name: str) -> Any:
|
|
244
|
-
"""A member the spec requires and never sends as null.
|
|
245
|
+
def _present(body: dict[str, Any], name: str, kind: type) -> Any:
|
|
246
|
+
"""A member the spec requires and never sends as null, of the type it gives.
|
|
245
247
|
|
|
246
248
|
A null one is the server's failure, raised for the caller to report as one, rather
|
|
247
249
|
than handed back in the answer: 2.7.1 listed a family whose `base` was null as a
|
|
248
|
-
family with no base (measured 2026-10-12).
|
|
250
|
+
family with no base (measured 2026-10-12). So is one of another type: 2.7.4 handed
|
|
251
|
+
back a version served as `"3"` as that string (measured 2026-10-16).
|
|
249
252
|
"""
|
|
250
253
|
value = body[name]
|
|
251
254
|
if value is None:
|
|
252
255
|
raise ValueError(f"{name} is null")
|
|
256
|
+
return _of_type(value, name, kind)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _nullable(body: dict[str, Any], name: str, kind: type, absent: bool = False) -> Any:
|
|
260
|
+
"""A member that may be null, or with `absent` left out, and is otherwise of `kind`."""
|
|
261
|
+
value = body.get(name) if absent else body[name]
|
|
262
|
+
return None if value is None else _of_type(value, name, kind)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _of_type(value: Any, name: str, kind: type) -> Any:
|
|
266
|
+
# A bool is an int to isinstance, and neither may stand for the other.
|
|
267
|
+
if isinstance(value, bool) != (kind is bool) or not isinstance(value, kind):
|
|
268
|
+
raise ValueError(f"{name} is not a {kind.__name__}: {value!r}")
|
|
253
269
|
return value
|
|
254
270
|
|
|
255
271
|
|
|
272
|
+
def _strings(values: list[Any], name: str) -> tuple[Any, ...]:
|
|
273
|
+
return tuple(_of_type(v, name, str) for v in values)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _sizes(sizes: Any, name: str) -> dict[str, int]:
|
|
277
|
+
return {fmt: _of_type(n, name, int) for fmt, n in _of_type(sizes, name, dict).items()}
|
|
278
|
+
|
|
279
|
+
|
|
256
280
|
def _optional_tuple(value: Any) -> tuple[Any, ...] | None:
|
|
257
281
|
return tuple(value) if value is not None else None
|
|
258
282
|
|
|
@@ -410,6 +410,50 @@ UNREADABLE_MEMBERS: dict[str, tuple[str, dict[str, Any]]] = {
|
|
|
410
410
|
),
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
+
|
|
414
|
+
def _with(member: str, value: Any, entry: dict[str, Any]) -> dict[str, Any]:
|
|
415
|
+
return {**entry, member: value}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _version_with(member: str, value: Any) -> dict[str, Any]:
|
|
419
|
+
family = database("bogon_ip")
|
|
420
|
+
return {**family, "versions": [_with(member, value, family["versions"][0])]}
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
_DOWNLOAD = cast(list[dict[str, Any]], DOWNLOADS["downloads"])[0]
|
|
424
|
+
|
|
425
|
+
# A member of another type than the spec gives it, which 2.7.4 handed back in the answer:
|
|
426
|
+
# a version served as "3" came back as that string (measured 2026-10-16).
|
|
427
|
+
UNREADABLE_MEMBERS.update(
|
|
428
|
+
{
|
|
429
|
+
"list version as a numeric string": (
|
|
430
|
+
LIST_PATH,
|
|
431
|
+
{"databases": [_version_with("version", "3")]},
|
|
432
|
+
),
|
|
433
|
+
"list version as true": (LIST_PATH, {"databases": [_version_with("version", True)]}),
|
|
434
|
+
"list format as a number": (LIST_PATH, {"databases": [_version_with("formats", [1])]}),
|
|
435
|
+
"list base as a number": (
|
|
436
|
+
LIST_PATH,
|
|
437
|
+
{"databases": [_with("base", 7, database("bogon_ip"))]},
|
|
438
|
+
),
|
|
439
|
+
"list open as a string": (
|
|
440
|
+
LIST_PATH,
|
|
441
|
+
{"databases": [_with("open", "false", database("bogon_ip"))]},
|
|
442
|
+
),
|
|
443
|
+
"list license_type as a number": (
|
|
444
|
+
LIST_PATH,
|
|
445
|
+
{"databases": [_with("license_type", 1, database("bogon_ip"))]},
|
|
446
|
+
),
|
|
447
|
+
"metadata entries as a string": (METADATA_PATH, _with("entries", "42", METADATA)),
|
|
448
|
+
"metadata size as a string": (METADATA_PATH, _with("size", {"csvgz": "760"}, METADATA)),
|
|
449
|
+
"downloads bytes as a string": (
|
|
450
|
+
DOWNLOADS_PATH,
|
|
451
|
+
{"downloads": [_with("bytes", "760", _DOWNLOAD)]},
|
|
452
|
+
),
|
|
453
|
+
"downloads sample as 1": (DOWNLOADS_PATH, {"downloads": [_with("sample", 1, _DOWNLOAD)]}),
|
|
454
|
+
}
|
|
455
|
+
)
|
|
456
|
+
|
|
413
457
|
MEMBER_CALLS = {LIST_PATH: "list", METADATA_PATH: "metadata", DOWNLOADS_PATH: "downloads"}
|
|
414
458
|
|
|
415
459
|
|
|
@@ -274,12 +274,44 @@ def test_the_api_key_is_trimmed(make_client: ClientFactory) -> None:
|
|
|
274
274
|
|
|
275
275
|
|
|
276
276
|
@pytest.mark.parametrize(
|
|
277
|
-
"api_key", ["key\r\nX-Injected: 1", "ke\ty", "key\x00", "key\x7f", "k\ney"]
|
|
277
|
+
"api_key", ["key\r\nX-Injected: 1", "ke\ty", "key\x00", "key\x7f", "k\ney", "k\x85ey", "kéy"]
|
|
278
278
|
)
|
|
279
|
-
def
|
|
279
|
+
def test_a_control_character_or_one_outside_ascii_is_refused_where_the_key_is_set(
|
|
280
280
|
make_client: ClientFactory, api_key: str
|
|
281
281
|
) -> None:
|
|
282
282
|
"""Accepted, a CR, LF or NUL failed every call as a retried network error with nothing
|
|
283
|
-
sent, and a tab or DEL went out on the wire inside the header (2.7.1).
|
|
284
|
-
with
|
|
283
|
+
sent, and a tab or DEL went out on the wire inside the header (2.7.1). A C1 control or
|
|
284
|
+
any other character outside ASCII failed every call with a raw UnicodeEncodeError and
|
|
285
|
+
nothing sent, since httpx encodes a header value as ASCII (2.7.3)."""
|
|
286
|
+
with pytest.raises(ValueError, match="control character or one outside ASCII"):
|
|
285
287
|
make_client(api_key=api_key)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@pytest.mark.parametrize("slashes", [1, 2, 3])
|
|
291
|
+
def test_every_trailing_slash_on_the_base_url_is_dropped(
|
|
292
|
+
make_client: ClientFactory, slashes: int
|
|
293
|
+
) -> None:
|
|
294
|
+
"""httpx drops one when it joins a path on and keeps the rest, so through 2.7.3 a
|
|
295
|
+
second reached the API as `//api/v2/database/list`, which prod answers with a
|
|
296
|
+
redirect this client does not follow."""
|
|
297
|
+
digests = {"md5": "m", "sha1": "s1", "sha256": "s256", "sha512": "s512"}
|
|
298
|
+
stub = Stub(
|
|
299
|
+
{
|
|
300
|
+
LIST_PATH: {"body": {"databases": []}},
|
|
301
|
+
CHECKSUM_PATH: {"body": {"id": "bogon_ip_v1", "format": "csvgz", "checksums": digests}},
|
|
302
|
+
"/.well-known/oauth-authorization-server": {
|
|
303
|
+
"body": {
|
|
304
|
+
"issuer": "https://api.example",
|
|
305
|
+
"authorization_endpoint": "https://api.example/oauth/authorize",
|
|
306
|
+
"token_endpoint": "https://api.example/oauth/token",
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
client = make_client(transport=stub.transport, base_url="https://api.example" + "/" * slashes)
|
|
312
|
+
|
|
313
|
+
client.database.list()
|
|
314
|
+
client.database.checksums("bogon_ip_v1", "csvgz")
|
|
315
|
+
client.oauth.metadata()
|
|
316
|
+
|
|
317
|
+
assert stub.paths() == [LIST_PATH, CHECKSUM_PATH, "/.well-known/oauth-authorization-server"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/account/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/api_keys/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/authorization/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/database_v_2/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/api/organization/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/account_org_ref.py
RENAMED
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/account_org_wrap.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/account_user.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_format.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/database_version.py
RENAMED
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/db_checksums.py
RENAMED
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/device_authorization.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/download_outcome.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/oauth_metadata.py
RENAMED
|
File without changes
|
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/revoke_request.py
RENAMED
|
File without changes
|
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/token_request.py
RENAMED
|
File without changes
|
{internetdata-2.7.3 → internetdata-2.7.5}/src/internetdata/_generated/models/token_response.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|