internetdata 2.7.5__tar.gz → 2.9.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 (98) hide show
  1. {internetdata-2.7.5 → internetdata-2.9.0}/PKG-INFO +15 -1
  2. {internetdata-2.7.5 → internetdata-2.9.0}/README.md +14 -0
  3. {internetdata-2.7.5 → internetdata-2.9.0}/spec/openapi.yaml +132 -9
  4. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/__init__.py +3 -1
  5. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_core.py +36 -5
  6. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/authorization/oauth_authorize.py +20 -10
  7. internetdata-2.9.0/src/internetdata/_generated/api/authorization/oauth_register.py +246 -0
  8. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/__init__.py +4 -0
  9. internetdata-2.9.0/src/internetdata/_generated/models/client_registration.py +126 -0
  10. internetdata-2.9.0/src/internetdata/_generated/models/client_registration_request.py +104 -0
  11. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/oauth_metadata.py +10 -0
  12. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/token_response.py +2 -1
  13. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/aio.py +29 -4
  14. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/client.py +39 -4
  15. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/models.py +37 -0
  16. {internetdata-2.7.5 → internetdata-2.9.0}/testdata/testdata.json +491 -0
  17. {internetdata-2.7.5 → internetdata-2.9.0}/tests/helpers.py +3 -0
  18. {internetdata-2.7.5 → internetdata-2.9.0}/tests/test_oauth.py +123 -1
  19. {internetdata-2.7.5 → internetdata-2.9.0}/.gitignore +0 -0
  20. {internetdata-2.7.5 → internetdata-2.9.0}/LICENSE +0 -0
  21. {internetdata-2.7.5 → internetdata-2.9.0}/pyproject.toml +0 -0
  22. {internetdata-2.7.5 → internetdata-2.9.0}/scripts/download-spec.sh +0 -0
  23. {internetdata-2.7.5 → internetdata-2.9.0}/scripts/generate.sh +0 -0
  24. {internetdata-2.7.5 → internetdata-2.9.0}/scripts/publish.sh +0 -0
  25. {internetdata-2.7.5 → internetdata-2.9.0}/scripts/v2_subset.py +0 -0
  26. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/__init__.py +0 -0
  27. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/__init__.py +0 -0
  28. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/account/__init__.py +0 -0
  29. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/account/account_identity.py +0 -0
  30. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/api_keys/__init__.py +0 -0
  31. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/api_keys/account_create_apikey.py +0 -0
  32. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/api_keys/account_list_apikeys.py +0 -0
  33. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/api_keys/account_reveal_apikey.py +0 -0
  34. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/api_keys/account_revoke_apikey.py +0 -0
  35. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/api_keys/account_rotate_apikey.py +0 -0
  36. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/authorization/__init__.py +0 -0
  37. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/authorization/oauth_device_authorization.py +0 -0
  38. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/authorization/oauth_metadata.py +0 -0
  39. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/authorization/oauth_revoke.py +0 -0
  40. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/authorization/oauth_token.py +0 -0
  41. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/database_v_2/__init__.py +0 -0
  42. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/database_v_2/database_checksum_v2.py +0 -0
  43. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/database_v_2/database_metadata_v2.py +0 -0
  44. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/database_v_2/download_database_v2.py +0 -0
  45. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/database_v_2/list_databases.py +0 -0
  46. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/database_v_2/list_downloads.py +0 -0
  47. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/organization/__init__.py +0 -0
  48. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/organization/account_org.py +0 -0
  49. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/api/organization/account_org_members.py +0 -0
  50. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/client.py +0 -0
  51. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/errors.py +0 -0
  52. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/account_create_apikey_request.py +0 -0
  53. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/account_created_apikey.py +0 -0
  54. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/account_org.py +0 -0
  55. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/account_org_ref.py +0 -0
  56. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/account_org_wrap.py +0 -0
  57. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/account_rc.py +0 -0
  58. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/account_revealed_apikey.py +0 -0
  59. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/account_user.py +0 -0
  60. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/apikey_detail.py +0 -0
  61. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/apikey_list.py +0 -0
  62. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/database.py +0 -0
  63. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/database_checksum_v2_response_200.py +0 -0
  64. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/database_format.py +0 -0
  65. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/database_license_type_type_1.py +0 -0
  66. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/database_license_type_type_2_type_1.py +0 -0
  67. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/database_license_type_type_3_type_1.py +0 -0
  68. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/database_metadata.py +0 -0
  69. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/database_metadata_column.py +0 -0
  70. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/database_metadata_sample.py +0 -0
  71. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/database_metadata_sample_additional_property_item.py +0 -0
  72. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/database_metadata_sample_size.py +0 -0
  73. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/database_metadata_schema.py +0 -0
  74. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/database_metadata_size.py +0 -0
  75. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/database_version.py +0 -0
  76. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/db_checksums.py +0 -0
  77. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/device_authorization.py +0 -0
  78. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/device_authorization_request.py +0 -0
  79. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/download.py +0 -0
  80. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/download_outcome.py +0 -0
  81. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/error.py +0 -0
  82. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/identity.py +0 -0
  83. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/list_databases_response_200.py +0 -0
  84. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/list_downloads_response_200.py +0 -0
  85. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/oauth_authorize_code_challenge_method.py +0 -0
  86. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/oauth_authorize_response_type.py +0 -0
  87. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/oauth_error.py +0 -0
  88. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/oauth_revoke_response_200.py +0 -0
  89. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/revoke_request.py +0 -0
  90. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/standing.py +0 -0
  91. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/models/token_request.py +0 -0
  92. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/_generated/types.py +0 -0
  93. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/errors.py +0 -0
  94. {internetdata-2.7.5 → internetdata-2.9.0}/src/internetdata/py.typed +0 -0
  95. {internetdata-2.7.5 → internetdata-2.9.0}/tests/conftest.py +0 -0
  96. {internetdata-2.7.5 → internetdata-2.9.0}/tests/test_client.py +0 -0
  97. {internetdata-2.7.5 → internetdata-2.9.0}/tests/test_conformance.py +0 -0
  98. {internetdata-2.7.5 → internetdata-2.9.0}/tests/test_download.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: internetdata
3
- Version: 2.7.5
3
+ Version: 2.9.0
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
@@ -223,6 +223,20 @@ tokens = signin.oauth.exchange_authorization_code("your-client-id", code, pkce.v
223
223
 
224
224
  Check that `state` came back as you sent it before you exchange `code`, which works once. The client ID can also be the https URL of a client metadata document your app serves, and such an app is never handed a key, so `tokens.apikey` stays `None`.
225
225
 
226
+ ### Register a client
227
+
228
+ An app with no client ID, and no client metadata document to use as one, registers itself and signs in with the `client_id` it gets back:
229
+
230
+ ```python
231
+ client = InternetData()
232
+ registration = client.oauth.register(
233
+ ["http://127.0.0.1:8765/callback"], client_name="Your App"
234
+ )
235
+ # registration.client_id is the client ID for authorization_url and exchange_authorization_code.
236
+ ```
237
+
238
+ Each redirect URI must be https on a public host name, or http on a loopback address, which then matches any port. Nothing is kept until a person approves the client, so an app with no client ID saved can simply register again.
239
+
226
240
  ## Other Libraries
227
241
 
228
242
  There are official InternetData client libraries available for many languages including PHP, Python, Go, Java, and Ruby. See our GitHub at https://github.com/internetdata for more.
@@ -189,6 +189,20 @@ tokens = signin.oauth.exchange_authorization_code("your-client-id", code, pkce.v
189
189
 
190
190
  Check that `state` came back as you sent it before you exchange `code`, which works once. The client ID can also be the https URL of a client metadata document your app serves, and such an app is never handed a key, so `tokens.apikey` stays `None`.
191
191
 
192
+ ### Register a client
193
+
194
+ An app with no client ID, and no client metadata document to use as one, registers itself and signs in with the `client_id` it gets back:
195
+
196
+ ```python
197
+ client = InternetData()
198
+ registration = client.oauth.register(
199
+ ["http://127.0.0.1:8765/callback"], client_name="Your App"
200
+ )
201
+ # registration.client_id is the client ID for authorization_url and exchange_authorization_code.
202
+ ```
203
+
204
+ Each redirect URI must be https on a public host name, or http on a loopback address, which then matches any port. Nothing is kept until a person approves the client, so an app with no client ID saved can simply register again.
205
+
192
206
  ## Other Libraries
193
207
 
194
208
  There are official InternetData client libraries available for many languages including PHP, Python, Go, Java, and Ruby. See our GitHub at https://github.com/internetdata for more.
@@ -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
15
+ version: 2026.10.16
16
16
  servers:
17
17
  - url: https://internetdata.io
18
18
  tags:
@@ -653,6 +653,56 @@ paths:
653
653
  application/json:
654
654
  schema:
655
655
  $ref: '#/components/schemas/OauthError'
656
+ /oauth/register:
657
+ post:
658
+ operationId: oauthRegister
659
+ tags:
660
+ - Authorization
661
+ summary: Register
662
+ description: |
663
+ RFC 7591 dynamic client registration, for a client that cannot use a
664
+ Client ID Metadata Document. Post your client's metadata as JSON, then
665
+ use the `client_id` in the answer at `/oauth/authorize` and
666
+ `/oauth/token`. Nothing is kept until a person approves your client, so
667
+ registering again costs nothing and leaves nothing to clean up.
668
+
669
+ Each redirect URI must be https on a public host name, or http on a
670
+ loopback address (`127.0.0.1`, `[::1]`, `localhost`), which then
671
+ matches on any port. A custom URI scheme is refused: use a loopback
672
+ redirect instead. So is a host on one of our own domains.
673
+
674
+ `client_name` is shown to the person as your client's own claim, with
675
+ where its redirect goes beside it. A name that renders blank, or mixes
676
+ Latin letters with Cyrillic or Greek ones, is shown as an unnamed
677
+ application.
678
+
679
+ The client is public whatever you ask for: `token_endpoint_auth_method`
680
+ comes back `none`, there is no client secret, and every authorization
681
+ proves itself with PKCE. It may be granted `apikeys.use` at most.
682
+ security: []
683
+ requestBody:
684
+ required: true
685
+ content:
686
+ application/json:
687
+ schema:
688
+ $ref: '#/components/schemas/ClientRegistrationRequest'
689
+ responses:
690
+ '201':
691
+ description: The client, as registered.
692
+ content:
693
+ application/json:
694
+ schema:
695
+ $ref: '#/components/schemas/ClientRegistration'
696
+ '400':
697
+ description: |
698
+ `invalid_redirect_uri` names the redirect URI refused and why, and
699
+ `invalid_client_metadata` anything else, such as a client that
700
+ does not use the authorization code flow. `slow_down` means this
701
+ address registered too many clients in the last minute.
702
+ content:
703
+ application/json:
704
+ schema:
705
+ $ref: '#/components/schemas/OauthError'
656
706
  /oauth/authorize:
657
707
  get:
658
708
  operationId: oauthAuthorize
@@ -669,13 +719,18 @@ paths:
669
719
  with `token_endpoint_auth_method` `none` and your `redirect_uris`. An
670
720
  https redirect URI must be on the same origin as the `client_id`; a
671
721
  loopback one (`http://127.0.0.1`, `http://[::1]`, `http://localhost`)
672
- matches on any port. Such a client is granted `apikeys.use` at most.
722
+ matches on any port. Such a client is granted `apikeys.use` at most. A
723
+ client registered at `/oauth/register` uses the `client_id` it was
724
+ given and one of the `redirect_uris` it registered.
673
725
 
674
726
  An unknown `client_id` or an unregistered `redirect_uri` is shown to the
675
- USER and never redirected, because sending an error to an address we
676
- have not verified belongs to you is how an open redirector works.
677
- Everything else comes back to your `redirect_uri` with `error`, your
678
- `state`, and `iss`.
727
+ person and never redirected, because sending an error to an address we
728
+ have not verified belongs to you is how an open redirector works. Any
729
+ other error in the request is shown to the person too: anyone can make a
730
+ client, so its `redirect_uri` is verified only as its own (RFC 9700
731
+ section 4.11.2). Only the person's answer comes back to your
732
+ `redirect_uri`: a `code`, or `error` `access_denied`, each with your
733
+ `state` and `iss`.
679
734
  security: []
680
735
  parameters:
681
736
  - name: client_id
@@ -731,9 +786,9 @@ paths:
731
786
  given that server.
732
787
  responses:
733
788
  '302':
734
- description: A redirect to the consent screen, or back to your redirect_uri with an error.
789
+ description: A redirect to the consent screen.
735
790
  '400':
736
- description: Shown to the user. An unverified redirect target is never sent an error.
791
+ description: Shown to the person. No error in the request is sent to your redirect_uri.
737
792
  '429':
738
793
  description: Shown to the user. Too many authorizations from one address.
739
794
  /oauth/token:
@@ -1485,6 +1540,9 @@ components:
1485
1540
  client_id_metadata_document_supported:
1486
1541
  type: boolean
1487
1542
  description: Any client may sign in with an https `client_id` serving its own metadata.
1543
+ registration_endpoint:
1544
+ type: string
1545
+ description: RFC 7591 dynamic client registration, for a client that cannot serve its own metadata.
1488
1546
  token_endpoint_auth_methods_supported:
1489
1547
  type: array
1490
1548
  items:
@@ -1600,7 +1658,8 @@ components:
1600
1658
  type: string
1601
1659
  description: |
1602
1660
  Not part of OAuth, and only for our own clients: a client that signed
1603
- in with a Client ID Metadata Document never receives a key. The API
1661
+ in with a Client ID Metadata Document or registered itself never
1662
+ receives a key. The API
1604
1663
  key itself, so a device ends up holding an ordinary key. Returned by
1605
1664
  the device code and authorization code grants only, never by a
1606
1665
  refresh, and only alongside
@@ -1611,6 +1670,70 @@ components:
1611
1670
  - access_token
1612
1671
  - token_type
1613
1672
  - expires_in
1673
+ ClientRegistrationRequest:
1674
+ type: object
1675
+ properties:
1676
+ redirect_uris:
1677
+ type: array
1678
+ items:
1679
+ type: string
1680
+ description: One to ten, each matched exactly, apart from a loopback one's port.
1681
+ client_name:
1682
+ type: string
1683
+ description: >-
1684
+ Shown to the person as your client's own claim, marked unverified, beside where its
1685
+ redirect goes.
1686
+ grant_types:
1687
+ type: array
1688
+ items:
1689
+ type: string
1690
+ description: Must include `authorization_code` when given.
1691
+ response_types:
1692
+ type: array
1693
+ items:
1694
+ type: string
1695
+ description: Must include `code` when given.
1696
+ token_endpoint_auth_method:
1697
+ type: string
1698
+ description: Read as `none`, whatever is sent.
1699
+ required:
1700
+ - redirect_uris
1701
+ ClientRegistration:
1702
+ type: object
1703
+ properties:
1704
+ client_id:
1705
+ type: string
1706
+ client_id_issued_at:
1707
+ type: integer
1708
+ description: Unix seconds.
1709
+ client_name:
1710
+ type: string
1711
+ description: As the person will see it.
1712
+ redirect_uris:
1713
+ type: array
1714
+ items:
1715
+ type: string
1716
+ grant_types:
1717
+ type: array
1718
+ items:
1719
+ type: string
1720
+ description: '`authorization_code` and `refresh_token`.'
1721
+ response_types:
1722
+ type: array
1723
+ items:
1724
+ type: string
1725
+ token_endpoint_auth_method:
1726
+ type: string
1727
+ description: Always `none`.
1728
+ scope:
1729
+ type: string
1730
+ description: 'The most this client may be granted: `apikeys.use`.'
1731
+ required:
1732
+ - client_id
1733
+ - client_id_issued_at
1734
+ - client_name
1735
+ - redirect_uris
1736
+ - token_endpoint_auth_method
1614
1737
  RevokeRequest:
1615
1738
  type: object
1616
1739
  properties:
@@ -24,6 +24,7 @@ from .models import (
24
24
  DATABASE_FORMATS,
25
25
  LICENSE_TYPES,
26
26
  STANDINGS,
27
+ ClientRegistration,
27
28
  Database,
28
29
  DatabaseMetadata,
29
30
  DatabaseVersion,
@@ -39,7 +40,7 @@ from .models import (
39
40
  TokenResponse,
40
41
  )
41
42
 
42
- __version__ = "2.7.5"
43
+ __version__ = "2.9.0"
43
44
 
44
45
  __all__ = [
45
46
  "DATABASE_FORMATS",
@@ -49,6 +50,7 @@ __all__ = [
49
50
  "AsyncDatabaseApi",
50
51
  "AsyncInternetData",
51
52
  "AsyncOauthApi",
53
+ "ClientRegistration",
52
54
  "Database",
53
55
  "DatabaseApi",
54
56
  "DatabaseMetadata",
@@ -14,7 +14,7 @@ import os
14
14
  import secrets
15
15
  import threading
16
16
  import time
17
- from collections.abc import Callable, Iterator
17
+ from collections.abc import Callable, Iterator, Sequence
18
18
  from pathlib import Path
19
19
  from types import ModuleType
20
20
  from typing import IO, Any, TypeVar, cast
@@ -36,6 +36,7 @@ OAUTH_DEVICE_AUTHORIZATION_PATH = "/oauth/device_authorization"
36
36
  OAUTH_TOKEN_PATH = "/oauth/token"
37
37
  OAUTH_REVOKE_PATH = "/oauth/revoke"
38
38
  OAUTH_AUTHORIZE_PATH = "/oauth/authorize"
39
+ OAUTH_REGISTER_PATH = "/oauth/register"
39
40
  DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code"
40
41
 
41
42
  # One chunk of a transfer, and therefore the ceiling on what a download of any size
@@ -348,10 +349,13 @@ def oauth_request(
348
349
  path: str,
349
350
  form: dict[str, str] | None,
350
351
  bound: float | None,
352
+ body: dict[str, Any] | None = None,
351
353
  ) -> httpx.Response:
352
- """One attempt at an OAuth endpoint, carrying no credential, its failure raised."""
354
+ """One attempt at an OAuth endpoint, carrying no credential, its failure raised. `body`
355
+ goes as JSON, for `register`; every other one sends a form or nothing."""
353
356
  http = client.get_httpx_client()
354
- return oauth_checked(exchange(http, _oauth_build(http, method, path, form, bound), bound))
357
+ req = _oauth_build(http, method, path, form, bound, body)
358
+ return oauth_checked(exchange(http, req, bound))
355
359
 
356
360
 
357
361
  async def oauth_request_async(
@@ -360,13 +364,39 @@ async def oauth_request_async(
360
364
  path: str,
361
365
  form: dict[str, str] | None,
362
366
  bound: float | None,
367
+ body: dict[str, Any] | None = None,
363
368
  ) -> httpx.Response:
364
369
  """`oauth_request`, awaited."""
365
370
  http = client.get_async_httpx_client()
366
- req = _oauth_build(http, method, path, form, bound)
371
+ req = _oauth_build(http, method, path, form, bound, body)
367
372
  return oauth_checked(await exchange_async(http, req, bound))
368
373
 
369
374
 
375
+ def registration_body(redirect_uris: Sequence[str], client_name: str | None) -> dict[str, Any]:
376
+ """`register`'s JSON body: the redirect URIs in the order given, then `client_name`
377
+ unless it is None or empty. No URI at all, an empty one, or one with no UTF-8 (a lone
378
+ surrogate) is refused before anything is sent."""
379
+ if isinstance(redirect_uris, str) or not redirect_uris:
380
+ raise ValueError(f"redirect_uris must list at least one URI, not {redirect_uris!r}")
381
+ uris = list(redirect_uris)
382
+ for i, uri in enumerate(uris):
383
+ _check_text(f"redirect_uris[{i}]", uri)
384
+ body: dict[str, Any] = {"redirect_uris": uris}
385
+ if client_name:
386
+ _check_text("client_name", client_name)
387
+ body["client_name"] = client_name
388
+ return body
389
+
390
+
391
+ def _check_text(name: str, value: Any) -> None:
392
+ if not isinstance(value, str) or value == "":
393
+ raise ValueError(f"{name} must be a non-empty string, not {value!r}")
394
+ try:
395
+ value.encode("utf-8")
396
+ except UnicodeEncodeError as exc:
397
+ raise ValueError(f"{name} has no UTF-8 to send: {value!r}") from exc
398
+
399
+
370
400
  def authorization_url(
371
401
  base_url: str,
372
402
  client_id: str,
@@ -571,8 +601,9 @@ def _oauth_build(
571
601
  path: str,
572
602
  form: dict[str, str] | None,
573
603
  bound: float | None,
604
+ body: dict[str, Any] | None = None,
574
605
  ) -> httpx.Request:
575
- req = http.build_request(method, path, data=form, timeout=httpx.Timeout(bound))
606
+ req = http.build_request(method, path, data=form, json=body, timeout=httpx.Timeout(bound))
576
607
  req.headers.pop("authorization", None)
577
608
  return req
578
609
 
@@ -105,13 +105,18 @@ def sync_detailed(
105
105
  with `token_endpoint_auth_method` `none` and your `redirect_uris`. An
106
106
  https redirect URI must be on the same origin as the `client_id`; a
107
107
  loopback one (`http://127.0.0.1`, `http://[::1]`, `http://localhost`)
108
- matches on any port. Such a client is granted `apikeys.use` at most.
108
+ matches on any port. Such a client is granted `apikeys.use` at most. A
109
+ client registered at `/oauth/register` uses the `client_id` it was
110
+ given and one of the `redirect_uris` it registered.
109
111
 
110
112
  An unknown `client_id` or an unregistered `redirect_uri` is shown to the
111
- USER and never redirected, because sending an error to an address we
112
- have not verified belongs to you is how an open redirector works.
113
- Everything else comes back to your `redirect_uri` with `error`, your
114
- `state`, and `iss`.
113
+ person and never redirected, because sending an error to an address we
114
+ have not verified belongs to you is how an open redirector works. Any
115
+ other error in the request is shown to the person too: anyone can make a
116
+ client, so its `redirect_uri` is verified only as its own (RFC 9700
117
+ section 4.11.2). Only the person's answer comes back to your
118
+ `redirect_uri`: a `code`, or `error` `access_denied`, each with your
119
+ `state` and `iss`.
115
120
 
116
121
  Args:
117
122
  client_id (str):
@@ -172,13 +177,18 @@ async def asyncio_detailed(
172
177
  with `token_endpoint_auth_method` `none` and your `redirect_uris`. An
173
178
  https redirect URI must be on the same origin as the `client_id`; a
174
179
  loopback one (`http://127.0.0.1`, `http://[::1]`, `http://localhost`)
175
- matches on any port. Such a client is granted `apikeys.use` at most.
180
+ matches on any port. Such a client is granted `apikeys.use` at most. A
181
+ client registered at `/oauth/register` uses the `client_id` it was
182
+ given and one of the `redirect_uris` it registered.
176
183
 
177
184
  An unknown `client_id` or an unregistered `redirect_uri` is shown to the
178
- USER and never redirected, because sending an error to an address we
179
- have not verified belongs to you is how an open redirector works.
180
- Everything else comes back to your `redirect_uri` with `error`, your
181
- `state`, and `iss`.
185
+ person and never redirected, because sending an error to an address we
186
+ have not verified belongs to you is how an open redirector works. Any
187
+ other error in the request is shown to the person too: anyone can make a
188
+ client, so its `redirect_uri` is verified only as its own (RFC 9700
189
+ section 4.11.2). Only the person's answer comes back to your
190
+ `redirect_uri`: a `code`, or `error` `access_denied`, each with your
191
+ `state` and `iss`.
182
192
 
183
193
  Args:
184
194
  client_id (str):
@@ -0,0 +1,246 @@
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.client_registration import ClientRegistration
9
+ from ...models.client_registration_request import ClientRegistrationRequest
10
+ from ...models.oauth_error import OauthError
11
+ from ...types import Response
12
+
13
+
14
+ def _get_kwargs(
15
+ *,
16
+ body: ClientRegistrationRequest,
17
+ ) -> dict[str, Any]:
18
+ headers: dict[str, Any] = {}
19
+
20
+ _kwargs: dict[str, Any] = {
21
+ "method": "post",
22
+ "url": "/oauth/register",
23
+ }
24
+
25
+ _kwargs["json"] = body.to_dict()
26
+
27
+ headers["Content-Type"] = "application/json"
28
+
29
+ _kwargs["headers"] = headers
30
+ return _kwargs
31
+
32
+
33
+ def _parse_response(
34
+ *, client: AuthenticatedClient | Client, response: httpx.Response
35
+ ) -> ClientRegistration | OauthError | None:
36
+ if response.status_code == 201:
37
+ response_201 = ClientRegistration.from_dict(response.json())
38
+
39
+ return response_201
40
+
41
+ if response.status_code == 400:
42
+ response_400 = OauthError.from_dict(response.json())
43
+
44
+ return response_400
45
+
46
+ if client.raise_on_unexpected_status:
47
+ raise errors.UnexpectedStatus(response.status_code, response.content)
48
+ else:
49
+ return None
50
+
51
+
52
+ def _build_response(
53
+ *, client: AuthenticatedClient | Client, response: httpx.Response
54
+ ) -> Response[ClientRegistration | OauthError]:
55
+ return Response(
56
+ status_code=HTTPStatus(response.status_code),
57
+ content=response.content,
58
+ headers=response.headers,
59
+ parsed=_parse_response(client=client, response=response),
60
+ )
61
+
62
+
63
+ def sync_detailed(
64
+ *,
65
+ client: AuthenticatedClient | Client,
66
+ body: ClientRegistrationRequest,
67
+ ) -> Response[ClientRegistration | OauthError]:
68
+ """Register
69
+
70
+ RFC 7591 dynamic client registration, for a client that cannot use a
71
+ Client ID Metadata Document. Post your client's metadata as JSON, then
72
+ use the `client_id` in the answer at `/oauth/authorize` and
73
+ `/oauth/token`. Nothing is kept until a person approves your client, so
74
+ registering again costs nothing and leaves nothing to clean up.
75
+
76
+ Each redirect URI must be https on a public host name, or http on a
77
+ loopback address (`127.0.0.1`, `[::1]`, `localhost`), which then
78
+ matches on any port. A custom URI scheme is refused: use a loopback
79
+ redirect instead. So is a host on one of our own domains.
80
+
81
+ `client_name` is shown to the person as your client's own claim, with
82
+ where its redirect goes beside it. A name that renders blank, or mixes
83
+ Latin letters with Cyrillic or Greek ones, is shown as an unnamed
84
+ application.
85
+
86
+ The client is public whatever you ask for: `token_endpoint_auth_method`
87
+ comes back `none`, there is no client secret, and every authorization
88
+ proves itself with PKCE. It may be granted `apikeys.use` at most.
89
+
90
+ Args:
91
+ body (ClientRegistrationRequest):
92
+
93
+ Raises:
94
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
95
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
96
+
97
+ Returns:
98
+ Response[ClientRegistration | OauthError]
99
+ """
100
+
101
+ kwargs = _get_kwargs(
102
+ body=body,
103
+ )
104
+
105
+ response = client.get_httpx_client().request(
106
+ **kwargs,
107
+ )
108
+
109
+ return _build_response(client=client, response=response)
110
+
111
+
112
+ def sync(
113
+ *,
114
+ client: AuthenticatedClient | Client,
115
+ body: ClientRegistrationRequest,
116
+ ) -> ClientRegistration | OauthError | None:
117
+ """Register
118
+
119
+ RFC 7591 dynamic client registration, for a client that cannot use a
120
+ Client ID Metadata Document. Post your client's metadata as JSON, then
121
+ use the `client_id` in the answer at `/oauth/authorize` and
122
+ `/oauth/token`. Nothing is kept until a person approves your client, so
123
+ registering again costs nothing and leaves nothing to clean up.
124
+
125
+ Each redirect URI must be https on a public host name, or http on a
126
+ loopback address (`127.0.0.1`, `[::1]`, `localhost`), which then
127
+ matches on any port. A custom URI scheme is refused: use a loopback
128
+ redirect instead. So is a host on one of our own domains.
129
+
130
+ `client_name` is shown to the person as your client's own claim, with
131
+ where its redirect goes beside it. A name that renders blank, or mixes
132
+ Latin letters with Cyrillic or Greek ones, is shown as an unnamed
133
+ application.
134
+
135
+ The client is public whatever you ask for: `token_endpoint_auth_method`
136
+ comes back `none`, there is no client secret, and every authorization
137
+ proves itself with PKCE. It may be granted `apikeys.use` at most.
138
+
139
+ Args:
140
+ body (ClientRegistrationRequest):
141
+
142
+ Raises:
143
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
144
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
145
+
146
+ Returns:
147
+ ClientRegistration | OauthError
148
+ """
149
+
150
+ return sync_detailed(
151
+ client=client,
152
+ body=body,
153
+ ).parsed
154
+
155
+
156
+ async def asyncio_detailed(
157
+ *,
158
+ client: AuthenticatedClient | Client,
159
+ body: ClientRegistrationRequest,
160
+ ) -> Response[ClientRegistration | OauthError]:
161
+ """Register
162
+
163
+ RFC 7591 dynamic client registration, for a client that cannot use a
164
+ Client ID Metadata Document. Post your client's metadata as JSON, then
165
+ use the `client_id` in the answer at `/oauth/authorize` and
166
+ `/oauth/token`. Nothing is kept until a person approves your client, so
167
+ registering again costs nothing and leaves nothing to clean up.
168
+
169
+ Each redirect URI must be https on a public host name, or http on a
170
+ loopback address (`127.0.0.1`, `[::1]`, `localhost`), which then
171
+ matches on any port. A custom URI scheme is refused: use a loopback
172
+ redirect instead. So is a host on one of our own domains.
173
+
174
+ `client_name` is shown to the person as your client's own claim, with
175
+ where its redirect goes beside it. A name that renders blank, or mixes
176
+ Latin letters with Cyrillic or Greek ones, is shown as an unnamed
177
+ application.
178
+
179
+ The client is public whatever you ask for: `token_endpoint_auth_method`
180
+ comes back `none`, there is no client secret, and every authorization
181
+ proves itself with PKCE. It may be granted `apikeys.use` at most.
182
+
183
+ Args:
184
+ body (ClientRegistrationRequest):
185
+
186
+ Raises:
187
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
188
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
189
+
190
+ Returns:
191
+ Response[ClientRegistration | OauthError]
192
+ """
193
+
194
+ kwargs = _get_kwargs(
195
+ body=body,
196
+ )
197
+
198
+ response = await client.get_async_httpx_client().request(**kwargs)
199
+
200
+ return _build_response(client=client, response=response)
201
+
202
+
203
+ async def asyncio(
204
+ *,
205
+ client: AuthenticatedClient | Client,
206
+ body: ClientRegistrationRequest,
207
+ ) -> ClientRegistration | OauthError | None:
208
+ """Register
209
+
210
+ RFC 7591 dynamic client registration, for a client that cannot use a
211
+ Client ID Metadata Document. Post your client's metadata as JSON, then
212
+ use the `client_id` in the answer at `/oauth/authorize` and
213
+ `/oauth/token`. Nothing is kept until a person approves your client, so
214
+ registering again costs nothing and leaves nothing to clean up.
215
+
216
+ Each redirect URI must be https on a public host name, or http on a
217
+ loopback address (`127.0.0.1`, `[::1]`, `localhost`), which then
218
+ matches on any port. A custom URI scheme is refused: use a loopback
219
+ redirect instead. So is a host on one of our own domains.
220
+
221
+ `client_name` is shown to the person as your client's own claim, with
222
+ where its redirect goes beside it. A name that renders blank, or mixes
223
+ Latin letters with Cyrillic or Greek ones, is shown as an unnamed
224
+ application.
225
+
226
+ The client is public whatever you ask for: `token_endpoint_auth_method`
227
+ comes back `none`, there is no client secret, and every authorization
228
+ proves itself with PKCE. It may be granted `apikeys.use` at most.
229
+
230
+ Args:
231
+ body (ClientRegistrationRequest):
232
+
233
+ Raises:
234
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
235
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
236
+
237
+ Returns:
238
+ ClientRegistration | OauthError
239
+ """
240
+
241
+ return (
242
+ await asyncio_detailed(
243
+ client=client,
244
+ body=body,
245
+ )
246
+ ).parsed
@@ -10,6 +10,8 @@ from .account_revealed_apikey import AccountRevealedApikey
10
10
  from .account_user import AccountUser
11
11
  from .apikey_detail import ApikeyDetail
12
12
  from .apikey_list import ApikeyList
13
+ from .client_registration import ClientRegistration
14
+ from .client_registration_request import ClientRegistrationRequest
13
15
  from .database import Database
14
16
  from .database_checksum_v2_response_200 import DatabaseChecksumV2Response200
15
17
  from .database_format import DatabaseFormat
@@ -56,6 +58,8 @@ __all__ = (
56
58
  "AccountUser",
57
59
  "ApikeyDetail",
58
60
  "ApikeyList",
61
+ "ClientRegistration",
62
+ "ClientRegistrationRequest",
59
63
  "Database",
60
64
  "DatabaseChecksumV2Response200",
61
65
  "DatabaseFormat",