internetdata 2.6.2__tar.gz → 2.7.1__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 (95) hide show
  1. {internetdata-2.6.2 → internetdata-2.7.1}/PKG-INFO +1 -1
  2. {internetdata-2.6.2 → internetdata-2.7.1}/spec/openapi.yaml +28 -5
  3. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/__init__.py +1 -1
  4. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/api_keys/account_rotate_apikey.py +12 -4
  5. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/database_v_2/list_databases.py +4 -0
  6. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/database.py +14 -1
  7. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/download.py +10 -0
  8. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/models.py +12 -1
  9. {internetdata-2.6.2 → internetdata-2.7.1}/tests/helpers.py +1 -0
  10. {internetdata-2.6.2 → internetdata-2.7.1}/tests/test_client.py +16 -0
  11. {internetdata-2.6.2 → internetdata-2.7.1}/.gitignore +0 -0
  12. {internetdata-2.6.2 → internetdata-2.7.1}/LICENSE +0 -0
  13. {internetdata-2.6.2 → internetdata-2.7.1}/README.md +0 -0
  14. {internetdata-2.6.2 → internetdata-2.7.1}/pyproject.toml +0 -0
  15. {internetdata-2.6.2 → internetdata-2.7.1}/scripts/download-spec.sh +0 -0
  16. {internetdata-2.6.2 → internetdata-2.7.1}/scripts/generate.sh +0 -0
  17. {internetdata-2.6.2 → internetdata-2.7.1}/scripts/publish.sh +0 -0
  18. {internetdata-2.6.2 → internetdata-2.7.1}/scripts/v2_subset.py +0 -0
  19. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_core.py +0 -0
  20. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/__init__.py +0 -0
  21. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/__init__.py +0 -0
  22. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/account/__init__.py +0 -0
  23. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/account/account_identity.py +0 -0
  24. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/api_keys/__init__.py +0 -0
  25. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/api_keys/account_create_apikey.py +0 -0
  26. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/api_keys/account_list_apikeys.py +0 -0
  27. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/api_keys/account_reveal_apikey.py +0 -0
  28. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/api_keys/account_revoke_apikey.py +0 -0
  29. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/authorization/__init__.py +0 -0
  30. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/authorization/oauth_authorize.py +0 -0
  31. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/authorization/oauth_device_authorization.py +0 -0
  32. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/authorization/oauth_metadata.py +0 -0
  33. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/authorization/oauth_revoke.py +0 -0
  34. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/authorization/oauth_token.py +0 -0
  35. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/database_v_2/__init__.py +0 -0
  36. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/database_v_2/database_checksum_v2.py +0 -0
  37. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/database_v_2/database_metadata_v2.py +0 -0
  38. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/database_v_2/download_database_v2.py +0 -0
  39. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/database_v_2/list_downloads.py +0 -0
  40. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/organization/__init__.py +0 -0
  41. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/organization/account_org.py +0 -0
  42. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/api/organization/account_org_members.py +0 -0
  43. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/client.py +0 -0
  44. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/errors.py +0 -0
  45. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/__init__.py +0 -0
  46. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/account_create_apikey_request.py +0 -0
  47. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/account_created_apikey.py +0 -0
  48. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/account_org.py +0 -0
  49. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/account_org_ref.py +0 -0
  50. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/account_org_wrap.py +0 -0
  51. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/account_rc.py +0 -0
  52. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/account_revealed_apikey.py +0 -0
  53. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/account_user.py +0 -0
  54. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/apikey_detail.py +0 -0
  55. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/apikey_list.py +0 -0
  56. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/database_checksum_v2_response_200.py +0 -0
  57. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/database_format.py +0 -0
  58. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/database_license_type_type_1.py +0 -0
  59. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/database_license_type_type_2_type_1.py +0 -0
  60. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/database_license_type_type_3_type_1.py +0 -0
  61. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/database_metadata.py +0 -0
  62. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/database_metadata_column.py +0 -0
  63. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/database_metadata_sample.py +0 -0
  64. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/database_metadata_sample_additional_property_item.py +0 -0
  65. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/database_metadata_sample_size.py +0 -0
  66. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/database_metadata_schema.py +0 -0
  67. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/database_metadata_size.py +0 -0
  68. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/database_version.py +0 -0
  69. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/db_checksums.py +0 -0
  70. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/device_authorization.py +0 -0
  71. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/device_authorization_request.py +0 -0
  72. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/download_outcome.py +0 -0
  73. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/error.py +0 -0
  74. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/identity.py +0 -0
  75. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/list_databases_response_200.py +0 -0
  76. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/list_downloads_response_200.py +0 -0
  77. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/oauth_authorize_code_challenge_method.py +0 -0
  78. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/oauth_authorize_response_type.py +0 -0
  79. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/oauth_error.py +0 -0
  80. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/oauth_metadata.py +0 -0
  81. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/oauth_revoke_response_200.py +0 -0
  82. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/revoke_request.py +0 -0
  83. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/standing.py +0 -0
  84. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/token_request.py +0 -0
  85. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/models/token_response.py +0 -0
  86. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/_generated/types.py +0 -0
  87. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/aio.py +0 -0
  88. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/client.py +0 -0
  89. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/errors.py +0 -0
  90. {internetdata-2.6.2 → internetdata-2.7.1}/src/internetdata/py.typed +0 -0
  91. {internetdata-2.6.2 → internetdata-2.7.1}/testdata/testdata.json +0 -0
  92. {internetdata-2.6.2 → internetdata-2.7.1}/tests/conftest.py +0 -0
  93. {internetdata-2.6.2 → internetdata-2.7.1}/tests/test_conformance.py +0 -0
  94. {internetdata-2.6.2 → internetdata-2.7.1}/tests/test_download.py +0 -0
  95. {internetdata-2.6.2 → internetdata-2.7.1}/tests/test_oauth.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: internetdata
3
- Version: 2.6.2
3
+ Version: 2.7.1
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.03
15
+ version: 2026.10.09
16
16
  servers:
17
17
  - url: https://internetdata.io
18
18
  tags:
@@ -28,7 +28,9 @@ tags:
28
28
  database is usually fetched.
29
29
 
30
30
  Access is granted by CONTRACT, one dataset family at a time: there is no
31
- self-service purchase and no subscription behind it.
31
+ self-service purchase and no subscription behind it. The Open databases
32
+ are the exception: any organization downloads one with its key, licensed
33
+ or not, under CC BY-SA 4.0. `list` marks them `open`.
32
34
 
33
35
  A successful download answers `302` with a time-limited URL pointing
34
36
  straight at object storage. Follow the redirect (`curl -L`); the link
@@ -58,6 +60,7 @@ paths:
58
60
  The whole published catalog, with your organization's license beside
59
61
  each entry, so `standing` says whether a database is yours today
60
62
  (`licensed`), was (`expired`), or has never been bought (`unlicensed`).
63
+ An `open` database downloads whatever its `standing`.
61
64
  security:
62
65
  - ApiKeyQueryAuth: []
63
66
  - ApiKeyHeaderAuth: []
@@ -498,10 +501,13 @@ paths:
498
501
  summary: Rotate
499
502
  description: |
500
503
  Replaces the secret behind a key, keeping its id, name and settings.
501
- The previous secret stops working immediately.
504
+ The previous secret stops working immediately. The new secret comes
505
+ back in the answer, so this needs `apikeys.reveal` as well as
506
+ `apikeys.manage`.
502
507
  security:
503
508
  - BearerAuth:
504
509
  - apikeys.manage
510
+ - apikeys.reveal
505
511
  parameters:
506
512
  - $ref: '#/components/parameters/ApikeyIdParam'
507
513
  responses:
@@ -896,7 +902,8 @@ components:
896
902
  description: |
897
903
  Where your license for a database family stands today. `licensed` is a
898
904
  live grant, `expired` one whose term has ended, and `unlicensed` a
899
- database published but never bought.
905
+ database published but never bought. An Open database (`open`)
906
+ downloads whatever this says.
900
907
  enum:
901
908
  - licensed
902
909
  - expired
@@ -912,6 +919,7 @@ components:
912
919
  - name
913
920
  - summary
914
921
  - standing
922
+ - open
915
923
  - license_type
916
924
  - starts
917
925
  - expires
@@ -931,6 +939,14 @@ components:
931
939
  description: One line on what the newest version contains.
932
940
  standing:
933
941
  $ref: '#/components/schemas/Standing'
942
+ open:
943
+ type: boolean
944
+ description: |
945
+ An Open database: any organization downloads it, and fetches its
946
+ checksums, with no license, under CC BY-SA 4.0 (credit
947
+ InternetData, https://internetdata.io, and share what you build
948
+ from it under the same license). `standing` still reports your
949
+ own license, which grants more where you hold one.
934
950
  license_type:
935
951
  type: string
936
952
  nullable: true
@@ -1013,6 +1029,7 @@ components:
1013
1029
  - format
1014
1030
  - outcome
1015
1031
  - sample
1032
+ - open
1016
1033
  - bytes
1017
1034
  - http_status
1018
1035
  - apikey_id
@@ -1036,6 +1053,12 @@ components:
1036
1053
  sample:
1037
1054
  type: boolean
1038
1055
  description: The evaluation sample rather than the database itself.
1056
+ open:
1057
+ type: boolean
1058
+ description: |
1059
+ Taken under the Open license rather than one of your licenses: an
1060
+ Open database downloaded while your organization held no license
1061
+ in term for it.
1039
1062
  bytes:
1040
1063
  type: integer
1041
1064
  format: int64
@@ -1624,7 +1647,7 @@ components:
1624
1647
  V2NotLicensed:
1625
1648
  description: |
1626
1649
  Your organization holds no license for this database, or its term has
1627
- ended.
1650
+ ended. Never for an Open database.
1628
1651
  `rc`: `NOT_LICENSED`, `LICENSE_EXPIRED`.
1629
1652
  content:
1630
1653
  application/json:
@@ -39,7 +39,7 @@ from .models import (
39
39
  TokenResponse,
40
40
  )
41
41
 
42
- __version__ = "2.6.2"
42
+ __version__ = "2.7.1"
43
43
 
44
44
  __all__ = [
45
45
  "DATABASE_FORMATS",
@@ -74,7 +74,9 @@ def sync_detailed(
74
74
  """Rotate
75
75
 
76
76
  Replaces the secret behind a key, keeping its id, name and settings.
77
- The previous secret stops working immediately.
77
+ The previous secret stops working immediately. The new secret comes
78
+ back in the answer, so this needs `apikeys.reveal` as well as
79
+ `apikeys.manage`.
78
80
 
79
81
  Args:
80
82
  id (UUID):
@@ -106,7 +108,9 @@ def sync(
106
108
  """Rotate
107
109
 
108
110
  Replaces the secret behind a key, keeping its id, name and settings.
109
- The previous secret stops working immediately.
111
+ The previous secret stops working immediately. The new secret comes
112
+ back in the answer, so this needs `apikeys.reveal` as well as
113
+ `apikeys.manage`.
110
114
 
111
115
  Args:
112
116
  id (UUID):
@@ -133,7 +137,9 @@ async def asyncio_detailed(
133
137
  """Rotate
134
138
 
135
139
  Replaces the secret behind a key, keeping its id, name and settings.
136
- The previous secret stops working immediately.
140
+ The previous secret stops working immediately. The new secret comes
141
+ back in the answer, so this needs `apikeys.reveal` as well as
142
+ `apikeys.manage`.
137
143
 
138
144
  Args:
139
145
  id (UUID):
@@ -163,7 +169,9 @@ async def asyncio(
163
169
  """Rotate
164
170
 
165
171
  Replaces the secret behind a key, keeping its id, name and settings.
166
- The previous secret stops working immediately.
172
+ The previous secret stops working immediately. The new secret comes
173
+ back in the answer, so this needs `apikeys.reveal` as well as
174
+ `apikeys.manage`.
167
175
 
168
176
  Args:
169
177
  id (UUID):
@@ -59,6 +59,7 @@ def sync_detailed(
59
59
  The whole published catalog, with your organization's license beside
60
60
  each entry, so `standing` says whether a database is yours today
61
61
  (`licensed`), was (`expired`), or has never been bought (`unlicensed`).
62
+ An `open` database downloads whatever its `standing`.
62
63
 
63
64
  Raises:
64
65
  errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
@@ -86,6 +87,7 @@ def sync(
86
87
  The whole published catalog, with your organization's license beside
87
88
  each entry, so `standing` says whether a database is yours today
88
89
  (`licensed`), was (`expired`), or has never been bought (`unlicensed`).
90
+ An `open` database downloads whatever its `standing`.
89
91
 
90
92
  Raises:
91
93
  errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
@@ -109,6 +111,7 @@ async def asyncio_detailed(
109
111
  The whole published catalog, with your organization's license beside
110
112
  each entry, so `standing` says whether a database is yours today
111
113
  (`licensed`), was (`expired`), or has never been bought (`unlicensed`).
114
+ An `open` database downloads whatever its `standing`.
112
115
 
113
116
  Raises:
114
117
  errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
@@ -134,6 +137,7 @@ async def asyncio(
134
137
  The whole published catalog, with your organization's license beside
135
138
  each entry, so `standing` says whether a database is yours today
136
139
  (`licensed`), was (`expired`), or has never been bought (`unlicensed`).
140
+ An `open` database downloads whatever its `standing`.
137
141
 
138
142
  Raises:
139
143
  errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
@@ -31,7 +31,13 @@ class Database:
31
31
  summary (str): One line on what the newest version contains.
32
32
  standing (Standing): Where your license for a database family stands today. `licensed` is a
33
33
  live grant, `expired` one whose term has ended, and `unlicensed` a
34
- database published but never bought.
34
+ database published but never bought. An Open database (`open`)
35
+ downloads whatever this says.
36
+ open_ (bool): An Open database: any organization downloads it, and fetches its
37
+ checksums, with no license, under CC BY-SA 4.0 (credit
38
+ InternetData, https://internetdata.io, and share what you build
39
+ from it under the same license). `standing` still reports your
40
+ own license, which grants more where you hold one.
35
41
  license_type (DatabaseLicenseTypeType1 | DatabaseLicenseTypeType2Type1 | DatabaseLicenseTypeType3Type1 | None):
36
42
  What your license permits you to do with the data. Null when there
37
43
  is no license, which is every family with standing `unlicensed`.
@@ -51,6 +57,7 @@ class Database:
51
57
  name: str
52
58
  summary: str
53
59
  standing: Standing
60
+ open_: bool
54
61
  license_type: (
55
62
  DatabaseLicenseTypeType1
56
63
  | DatabaseLicenseTypeType2Type1
@@ -73,6 +80,8 @@ class Database:
73
80
 
74
81
  standing = self.standing.value
75
82
 
83
+ open_ = self.open_
84
+
76
85
  license_type: None | str
77
86
  if (
78
87
  isinstance(self.license_type, DatabaseLicenseTypeType1)
@@ -120,6 +129,7 @@ class Database:
120
129
  "name": name,
121
130
  "summary": summary,
122
131
  "standing": standing,
132
+ "open": open_,
123
133
  "license_type": license_type,
124
134
  "starts": starts,
125
135
  "expires": expires,
@@ -144,6 +154,8 @@ class Database:
144
154
 
145
155
  standing = Standing(d.pop("standing"))
146
156
 
157
+ open_ = d.pop("open")
158
+
147
159
  def _parse_license_type(
148
160
  data: object,
149
161
  ) -> (
@@ -260,6 +272,7 @@ class Database:
260
272
  name=name,
261
273
  summary=summary,
262
274
  standing=standing,
275
+ open_=open_,
263
276
  license_type=license_type,
264
277
  starts=starts,
265
278
  expires=expires,
@@ -21,6 +21,9 @@ class Download:
21
21
  format_ (str):
22
22
  outcome (DownloadOutcome):
23
23
  sample (bool): The evaluation sample rather than the database itself.
24
+ open_ (bool): Taken under the Open license rather than one of your licenses: an
25
+ Open database downloaded while your organization held no license
26
+ in term for it.
24
27
  bytes_ (int | None): Object size at redirect time, NOT bytes delivered: the transfer is a
25
28
  presigned redirect straight to object storage, so we never observe
26
29
  how much of it was taken.
@@ -35,6 +38,7 @@ class Download:
35
38
  format_: str
36
39
  outcome: DownloadOutcome
37
40
  sample: bool
41
+ open_: bool
38
42
  bytes_: int | None
39
43
  http_status: int | None
40
44
  apikey_id: None | str
@@ -52,6 +56,8 @@ class Download:
52
56
 
53
57
  sample = self.sample
54
58
 
59
+ open_ = self.open_
60
+
55
61
  bytes_: int | None
56
62
  bytes_ = self.bytes_
57
63
 
@@ -77,6 +83,7 @@ class Download:
77
83
  "format": format_,
78
84
  "outcome": outcome,
79
85
  "sample": sample,
86
+ "open": open_,
80
87
  "bytes": bytes_,
81
88
  "http_status": http_status,
82
89
  "apikey_id": apikey_id,
@@ -99,6 +106,8 @@ class Download:
99
106
 
100
107
  sample = d.pop("sample")
101
108
 
109
+ open_ = d.pop("open")
110
+
102
111
  def _parse_bytes_(data: object) -> int | None:
103
112
  if data is None:
104
113
  return data
@@ -141,6 +150,7 @@ class Download:
141
150
  format_=format_,
142
151
  outcome=outcome,
143
152
  sample=sample,
153
+ open_=open_,
144
154
  bytes_=bytes_,
145
155
  http_status=http_status,
146
156
  apikey_id=apikey_id,
@@ -86,7 +86,9 @@ class Database:
86
86
  """One database FAMILY, with your organization's license beside it.
87
87
 
88
88
  A family your organization has never licensed is still listed, with `standing` set to
89
- `unlicensed`, so you can see what else exists.
89
+ `unlicensed`, so you can see what else exists. One that is `open` downloads with no
90
+ license at all, under CC BY-SA 4.0, whatever its `standing`, which still reports your
91
+ own license.
90
92
 
91
93
  A rolling license carries `renews_at`, when it next renews, and `notice_due_at`, the
92
94
  last day notice of non-renewal can be given for the term ending then. Both are None
@@ -107,6 +109,9 @@ class Database:
107
109
  notice_due_at: datetime.datetime | None = field(default=None, kw_only=True)
108
110
  versions: tuple[DatabaseVersion, ...]
109
111
  raw: dict[str, Any] = field(default_factory=dict)
112
+ # Keyword-only with a default, so a Database built by hand before 2.7.0 still builds;
113
+ # the parser always sets it.
114
+ open: bool = field(default=False, kw_only=True)
110
115
 
111
116
 
112
117
  @dataclass(frozen=True, slots=True)
@@ -162,6 +167,10 @@ class Download:
162
167
  # with a default, so a Download built by hand before 2.5.0 still builds; the parser
163
168
  # always sets it.
164
169
  sample: bool = field(default=False, kw_only=True)
170
+ # Taken under the Open license rather than one of your licenses: an Open database
171
+ # downloaded while your organization held no license in term for it. Keyword-only
172
+ # with a default, so a Download built by hand before 2.7.0 still builds.
173
+ open: bool = field(default=False, kw_only=True)
165
174
 
166
175
 
167
176
  def to_database(body: dict[str, Any]) -> Database:
@@ -177,6 +186,7 @@ def to_database(body: dict[str, Any]) -> Database:
177
186
  notice_due_at=_datetime(body["notice_due_at"]),
178
187
  versions=tuple(to_version(v) for v in body["versions"]),
179
188
  raw=body,
189
+ open=body["open"],
180
190
  )
181
191
 
182
192
 
@@ -223,6 +233,7 @@ def to_download(body: dict[str, Any]) -> Download:
223
233
  client_ip=body["client_ip"],
224
234
  user_agent=body["user_agent"],
225
235
  sample=body["sample"],
236
+ open=body["open"],
226
237
  )
227
238
 
228
239
 
@@ -441,6 +441,7 @@ def database(base: str, **overrides: Any) -> dict[str, Any]:
441
441
  "name": base.replace("_", " ").title(),
442
442
  "summary": f"{base} rows",
443
443
  "standing": "licensed",
444
+ "open": False,
444
445
  "license_type": "standard",
445
446
  "starts": "2026-01-01T00:00:00.000Z",
446
447
  "expires": None,
@@ -88,6 +88,7 @@ DOWNLOADS = {
88
88
  "client_ip": "203.0.113.4",
89
89
  "user_agent": "curl/8.5.0",
90
90
  "sample": False,
91
+ "open": True,
91
92
  "created": "2026-09-04T10:00:00.000Z",
92
93
  },
93
94
  {
@@ -100,6 +101,7 @@ DOWNLOADS = {
100
101
  "client_ip": None,
101
102
  "user_agent": None,
102
103
  "sample": True,
104
+ "open": False,
103
105
  "created": "2026-09-04T09:00:00.000Z",
104
106
  },
105
107
  ]
@@ -202,6 +204,7 @@ def test_a_refusal_is_listed_beside_a_success(make_client: ClientFactory) -> Non
202
204
  assert denied.http_status == 403
203
205
  assert denied.apikey_id is None and denied.client_ip is None
204
206
  assert [a.sample for a in attempts] == [False, True]
207
+ assert [a.open for a in attempts] == [True, False]
205
208
 
206
209
 
207
210
  def test_the_downloads_limit_reaches_the_wire(make_client: ClientFactory) -> None:
@@ -225,6 +228,19 @@ def test_a_licence_with_no_end_date_reads_as_none(make_client: ClientFactory) ->
225
228
  assert family.notice_due_at is None
226
229
 
227
230
 
231
+ def test_an_open_family_is_read_beside_its_standing(make_client: ClientFactory) -> None:
232
+ served = [
233
+ database("asn", standing="unlicensed", open=True, license_type=None, starts=None),
234
+ database("vpn_ip"),
235
+ ]
236
+ stub = Stub({LIST_PATH: {"body": {"databases": served}}})
237
+ client = make_client(transport=stub.transport)
238
+
239
+ families = client.database.list()
240
+
241
+ assert [(f.open, f.standing) for f in families] == [(True, "unlicensed"), (False, "licensed")]
242
+
243
+
228
244
  def test_a_rolling_license_carries_its_renewal_dates(make_client: ClientFactory) -> None:
229
245
  served = database(
230
246
  "bogon_ip",
File without changes
File without changes
File without changes