internetdata 2.9.5__tar.gz → 2.9.6__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.9.5 → internetdata-2.9.6}/PKG-INFO +1 -1
  2. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/__init__.py +1 -1
  3. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/aio.py +11 -3
  4. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/client.py +11 -3
  5. {internetdata-2.9.5 → internetdata-2.9.6}/tests/helpers.py +5 -2
  6. {internetdata-2.9.5 → internetdata-2.9.6}/tests/test_oauth.py +44 -0
  7. {internetdata-2.9.5 → internetdata-2.9.6}/.gitignore +0 -0
  8. {internetdata-2.9.5 → internetdata-2.9.6}/LICENSE +0 -0
  9. {internetdata-2.9.5 → internetdata-2.9.6}/README.md +0 -0
  10. {internetdata-2.9.5 → internetdata-2.9.6}/pyproject.toml +0 -0
  11. {internetdata-2.9.5 → internetdata-2.9.6}/scripts/download-spec.sh +0 -0
  12. {internetdata-2.9.5 → internetdata-2.9.6}/scripts/generate.sh +0 -0
  13. {internetdata-2.9.5 → internetdata-2.9.6}/scripts/publish.sh +0 -0
  14. {internetdata-2.9.5 → internetdata-2.9.6}/scripts/v2_subset.py +0 -0
  15. {internetdata-2.9.5 → internetdata-2.9.6}/spec/openapi.yaml +0 -0
  16. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_core.py +0 -0
  17. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/__init__.py +0 -0
  18. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/__init__.py +0 -0
  19. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/account/__init__.py +0 -0
  20. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/account/account_identity.py +0 -0
  21. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/api_keys/__init__.py +0 -0
  22. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/api_keys/account_create_apikey.py +0 -0
  23. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/api_keys/account_list_apikeys.py +0 -0
  24. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/api_keys/account_reveal_apikey.py +0 -0
  25. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/api_keys/account_revoke_apikey.py +0 -0
  26. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/api_keys/account_rotate_apikey.py +0 -0
  27. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/authorization/__init__.py +0 -0
  28. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/authorization/oauth_authorize.py +0 -0
  29. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/authorization/oauth_device_authorization.py +0 -0
  30. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/authorization/oauth_metadata.py +0 -0
  31. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/authorization/oauth_register.py +0 -0
  32. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/authorization/oauth_revoke.py +0 -0
  33. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/authorization/oauth_token.py +0 -0
  34. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/database_v_2/__init__.py +0 -0
  35. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/database_v_2/database_checksum_v2.py +0 -0
  36. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/database_v_2/database_metadata_v2.py +0 -0
  37. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/database_v_2/download_database_v2.py +0 -0
  38. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/database_v_2/list_databases.py +0 -0
  39. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/database_v_2/list_downloads.py +0 -0
  40. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/organization/__init__.py +0 -0
  41. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/organization/account_org.py +0 -0
  42. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/api/organization/account_org_members.py +0 -0
  43. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/client.py +0 -0
  44. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/errors.py +0 -0
  45. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/__init__.py +0 -0
  46. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/account_create_apikey_request.py +0 -0
  47. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/account_created_apikey.py +0 -0
  48. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/account_org.py +0 -0
  49. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/account_org_ref.py +0 -0
  50. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/account_org_wrap.py +0 -0
  51. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/account_rc.py +0 -0
  52. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/account_revealed_apikey.py +0 -0
  53. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/account_user.py +0 -0
  54. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/apikey_detail.py +0 -0
  55. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/apikey_list.py +0 -0
  56. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/client_registration.py +0 -0
  57. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/client_registration_request.py +0 -0
  58. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/database.py +0 -0
  59. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/database_checksum_v2_response_200.py +0 -0
  60. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/database_format.py +0 -0
  61. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/database_license_type_type_1.py +0 -0
  62. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/database_license_type_type_2_type_1.py +0 -0
  63. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/database_license_type_type_3_type_1.py +0 -0
  64. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/database_metadata.py +0 -0
  65. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/database_metadata_column.py +0 -0
  66. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/database_metadata_sample.py +0 -0
  67. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/database_metadata_sample_additional_property_item.py +0 -0
  68. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/database_metadata_sample_size.py +0 -0
  69. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/database_metadata_schema.py +0 -0
  70. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/database_metadata_size.py +0 -0
  71. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/database_version.py +0 -0
  72. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/db_checksums.py +0 -0
  73. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/device_authorization.py +0 -0
  74. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/device_authorization_request.py +0 -0
  75. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/download.py +0 -0
  76. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/download_outcome.py +0 -0
  77. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/error.py +0 -0
  78. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/identity.py +0 -0
  79. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/list_databases_response_200.py +0 -0
  80. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/list_downloads_response_200.py +0 -0
  81. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/oauth_authorize_code_challenge_method.py +0 -0
  82. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/oauth_authorize_response_type.py +0 -0
  83. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/oauth_error.py +0 -0
  84. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/oauth_metadata.py +0 -0
  85. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/oauth_revoke_response_200.py +0 -0
  86. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/revoke_request.py +0 -0
  87. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/standing.py +0 -0
  88. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/token_request.py +0 -0
  89. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/models/token_response.py +0 -0
  90. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/_generated/types.py +0 -0
  91. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/errors.py +0 -0
  92. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/models.py +0 -0
  93. {internetdata-2.9.5 → internetdata-2.9.6}/src/internetdata/py.typed +0 -0
  94. {internetdata-2.9.5 → internetdata-2.9.6}/testdata/testdata.json +0 -0
  95. {internetdata-2.9.5 → internetdata-2.9.6}/tests/conftest.py +0 -0
  96. {internetdata-2.9.5 → internetdata-2.9.6}/tests/test_client.py +0 -0
  97. {internetdata-2.9.5 → internetdata-2.9.6}/tests/test_conformance.py +0 -0
  98. {internetdata-2.9.5 → internetdata-2.9.6}/tests/test_download.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: internetdata
3
- Version: 2.9.5
3
+ Version: 2.9.6
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
@@ -40,7 +40,7 @@ from .models import (
40
40
  TokenResponse,
41
41
  )
42
42
 
43
- __version__ = "2.9.5"
43
+ __version__ = "2.9.6"
44
44
 
45
45
  __all__ = [
46
46
  "DATABASE_FORMATS",
@@ -478,9 +478,17 @@ class AsyncOauthApi:
478
478
  while True:
479
479
  # No wait runs past the deadline: an interval ending after it waits only the
480
480
  # time left, and the expiry follows with nothing sent. In full, an interval of
481
- # 2147483647 held a poll with 2 s left for 68 years (2.6.0).
482
- await self._clock.sleep(min(interval, max(deadline - self._clock.now(), 0)))
483
- if self._clock.now() >= deadline:
481
+ # 2147483647 held a poll with 2 s left for 68 years (2.6.0). A remainder not
482
+ # above 0 is none left: a hand-built `expires_in` of NaN, which compares false
483
+ # with everything, polled every interval for good (2.9.5). `max(0, ...)` is 0
484
+ # for NaN only in that order; `max(nan, 0)` is NaN.
485
+ left = max(0, deadline - self._clock.now())
486
+ to_deadline = left <= interval
487
+ await self._clock.sleep(left if to_deadline else interval)
488
+ # A wait that ran to the deadline ends the poll without reading the clock again:
489
+ # a timer may wake before the clock has moved the whole wait, and the deadline
490
+ # would then read as not yet reached, so one more request would go out at it.
491
+ if to_deadline or self._clock.now() >= deadline:
484
492
  raise OauthExpiredTokenError()
485
493
  try:
486
494
  return await self.exchange_device_code(
@@ -537,9 +537,17 @@ class OauthApi:
537
537
  while True:
538
538
  # No wait runs past the deadline: an interval ending after it waits only the
539
539
  # time left, and the expiry follows with nothing sent. In full, an interval of
540
- # 2147483647 held a poll with 2 s left for 68 years (2.6.0).
541
- self._clock.sleep(min(interval, max(deadline - self._clock.now(), 0)))
542
- if self._clock.now() >= deadline:
540
+ # 2147483647 held a poll with 2 s left for 68 years (2.6.0). A remainder not
541
+ # above 0 is none left: a hand-built `expires_in` of NaN, which compares false
542
+ # with everything, polled every interval for good (2.9.5). `max(0, ...)` is 0
543
+ # for NaN only in that order; `max(nan, 0)` is NaN.
544
+ left = max(0, deadline - self._clock.now())
545
+ to_deadline = left <= interval
546
+ self._clock.sleep(left if to_deadline else interval)
547
+ # A wait that ran to the deadline ends the poll without reading the clock again:
548
+ # a timer may wake before the clock has moved the whole wait, and the deadline
549
+ # would then read as not yet reached, so one more request would go out at it.
550
+ if to_deadline or self._clock.now() >= deadline:
543
551
  raise OauthExpiredTokenError()
544
552
  try:
545
553
  return self.exchange_device_code(client_id, device.device_code, timeout=timeout)
@@ -222,11 +222,14 @@ class FakeClock(Clock):
222
222
  """A poll's sleep and monotonic clock, replaced together, recording every wait in
223
223
  seconds. Past LOOP_BOUND waits, or twice that many clock reads, it trips `bound`."""
224
224
 
225
- def __init__(self, bound: LoopBound) -> None:
225
+ def __init__(self, bound: LoopBound, short: float = 0.0) -> None:
226
226
  self.waits: list[float] = []
227
227
  self._elapsed = 0.0
228
228
  self._reads = 0
229
229
  self._bound = bound
230
+ # Each wait of at least 1 ms wakes this many seconds before the clock has moved
231
+ # its delay, as a runtime timer may.
232
+ self._short = short
230
233
 
231
234
  def now(self) -> float:
232
235
  self._reads += 1
@@ -238,7 +241,7 @@ class FakeClock(Clock):
238
241
  if len(self.waits) == LOOP_BOUND:
239
242
  self._bound.trip(f"waited more than {LOOP_BOUND} times")
240
243
  self.waits.append(seconds)
241
- self._elapsed += seconds
244
+ self._elapsed += seconds - self._short if seconds >= 0.001 else seconds
242
245
 
243
246
 
244
247
  class AsyncFakeClock(AsyncClock):
@@ -9,6 +9,7 @@ from __future__ import annotations
9
9
 
10
10
  import asyncio
11
11
  import json
12
+ import math
12
13
  import re
13
14
  import time
14
15
  import urllib.parse
@@ -389,6 +390,49 @@ def test_an_expires_in_past_any_float_still_polls(make_client: ClientFactory) ->
389
390
  assert clock.waits == [1]
390
391
 
391
392
 
393
+ @pytest.mark.parametrize("interval", [5, 3])
394
+ def test_a_wait_that_ran_to_the_deadline_ends_the_poll_however_early_the_timer_woke(
395
+ make_client: ClientFactory, interval: int
396
+ ) -> None:
397
+ """A timer that wakes before the clock has moved its delay leaves the deadline reading as
398
+ not yet reached, so a poll that read the clock again would ask once more at it. A wait
399
+ cut to the deadline, or ending at it, ends the poll."""
400
+ bound = LoopBound()
401
+ stub = OauthStub([{"status": 400, "body": {"error": "authorization_pending"}}], bound)
402
+ client = make_client(base_url=BASE_URL, transport=stub.transport)
403
+ clock = FakeClock(bound, short=0.0005)
404
+ client.oauth.use_clock(clock)
405
+ device = DeviceAuthorization(**{**_device_fields(), "expires_in": 3, "interval": interval})
406
+
407
+ outcome = settle(lambda: client.oauth.poll_device_token("internetdata-cli", device), bound)
408
+
409
+ assert isinstance(outcome, OauthExpiredTokenError), f"settled with {outcome!r}"
410
+ assert outcome.status is None, "the local deadline, not the server's"
411
+ assert stub.requests == []
412
+ assert clock.waits == [3]
413
+
414
+
415
+ @pytest.mark.parametrize("expires_in", [math.nan, -math.nan, math.inf - math.inf])
416
+ def test_a_lifetime_that_is_not_a_number_ends_the_poll_at_once(
417
+ make_client: ClientFactory, expires_in: float
418
+ ) -> None:
419
+ """A hand-built device can carry NaN, which compares false with everything, so the
420
+ deadline was never reached and the poll asked every interval for good."""
421
+ bound = LoopBound()
422
+ stub = OauthStub([{"status": 400, "body": {"error": "authorization_pending"}}], bound)
423
+ client = make_client(base_url=BASE_URL, transport=stub.transport)
424
+ clock = FakeClock(bound)
425
+ client.oauth.use_clock(clock)
426
+ device = DeviceAuthorization(**{**_device_fields(), "expires_in": expires_in, "interval": 1})
427
+
428
+ outcome = settle(lambda: client.oauth.poll_device_token("internetdata-cli", device), bound)
429
+
430
+ assert isinstance(outcome, OauthExpiredTokenError), f"settled with {outcome!r}"
431
+ assert outcome.status is None, "the local deadline, not the server's"
432
+ assert stub.requests == []
433
+ assert clock.waits == [0]
434
+
435
+
392
436
  def _device_fields() -> dict[str, Any]:
393
437
  return {
394
438
  "device_code": "mo_dc_x",
File without changes
File without changes
File without changes