kiba-core 0.5.3.dev53__tar.gz → 0.5.3.dev55__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 (99) hide show
  1. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/CHANGELOG.md +1 -0
  2. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/PKG-INFO +1 -1
  3. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/api/authorizer.py +2 -1
  4. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/api/middleware/exception_handling_middleware.py +2 -4
  5. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/exceptions.py +43 -1
  6. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/requester/requester.py +1 -1
  7. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/kiba_core.egg-info/PKG-INFO +1 -1
  8. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/pyproject.toml +1 -1
  9. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/uv.lock +393 -374
  10. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/.github/pull_request_template.md +0 -0
  11. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/.github/workflows/deploy.yml +0 -0
  12. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/.github/workflows/pull-request.yml +0 -0
  13. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/.github/workflows/release.yml +0 -0
  14. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/.gitignore +0 -0
  15. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/Dockerfile +0 -0
  16. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/README.md +0 -0
  17. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/__init__.py +0 -0
  18. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/api/__init__.py +0 -0
  19. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/api/api_request.py +0 -0
  20. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/api/api_response.py +0 -0
  21. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/api/default_routes.py +0 -0
  22. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/api/health.py +0 -0
  23. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/api/json_route.py +0 -0
  24. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/api/middleware/__init__.py +0 -0
  25. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/api/middleware/database_connection_middleware.py +0 -0
  26. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/api/middleware/logging_middleware.py +0 -0
  27. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/api/middleware/server_headers_middleware.py +0 -0
  28. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/api/streaming_json_route.py +0 -0
  29. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/aws_requester.py +0 -0
  30. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/caching/__init__.py +0 -0
  31. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/caching/cache.py +0 -0
  32. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/caching/dict_cache.py +0 -0
  33. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/caching/file_cache.py +0 -0
  34. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/http/__init__.py +0 -0
  35. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/http/basic_authentication.py +0 -0
  36. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/http/jwt.py +0 -0
  37. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/http/rest_method.py +0 -0
  38. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/logging.py +0 -0
  39. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/notifications/__init__.py +0 -0
  40. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/notifications/discord_client.py +0 -0
  41. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/notifications/notification_client.py +0 -0
  42. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/notifications/slack_client.py +0 -0
  43. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/py.typed +0 -0
  44. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/queues/__init__.py +0 -0
  45. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/queues/aqs.py +0 -0
  46. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/queues/cosmos.py +0 -0
  47. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/queues/message_queue.py +0 -0
  48. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/queues/message_queue_processor.py +0 -0
  49. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/queues/model.py +0 -0
  50. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/queues/sql.py +0 -0
  51. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/queues/sqs.py +0 -0
  52. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/requester/__init__.py +0 -0
  53. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/s3_manager.py +0 -0
  54. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/service_client.py +0 -0
  55. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/store/__init__.py +0 -0
  56. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/store/database.py +0 -0
  57. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/store/retriever.py +0 -0
  58. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/store/saver.py +0 -0
  59. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/util/__init__.py +0 -0
  60. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/util/async_util.py +0 -0
  61. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/util/chain_util.py +0 -0
  62. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/util/date_util.py +0 -0
  63. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/util/dict_util.py +0 -0
  64. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/util/file_util.py +0 -0
  65. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/util/hashing_util.py +0 -0
  66. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/util/http_util.py +0 -0
  67. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/util/json_util.py +0 -0
  68. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/util/list_util.py +0 -0
  69. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/util/string_util.py +0 -0
  70. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/util/typing_util.py +0 -0
  71. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/util/url_util.py +0 -0
  72. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/util/value_holder.py +0 -0
  73. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/web3/__init__.py +0 -0
  74. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/web3/eth_client.py +0 -0
  75. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/core/web3/multicall3.py +0 -0
  76. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/kiba_core.egg-info/SOURCES.txt +0 -0
  77. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/kiba_core.egg-info/dependency_links.txt +0 -0
  78. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/kiba_core.egg-info/requires.txt +0 -0
  79. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/kiba_core.egg-info/top_level.txt +0 -0
  80. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/makefile +0 -0
  81. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/setup.cfg +0 -0
  82. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/__init__.py +0 -0
  83. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/api/test_authorizer.py +0 -0
  84. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/api/test_json_route.py +0 -0
  85. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/api/test_streaming_json_route.py +0 -0
  86. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/caching/test_dict_cache.py +0 -0
  87. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/caching/test_file_cache.py +0 -0
  88. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/util/__init__.py +0 -0
  89. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/util/test_async_util.py +0 -0
  90. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/util/test_chain_util.py +0 -0
  91. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/util/test_date_util.py +0 -0
  92. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/util/test_dict_util.py +0 -0
  93. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/util/test_file_util.py +0 -0
  94. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/util/test_json_util.py +0 -0
  95. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/util/test_list_util.py +0 -0
  96. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/util/test_string_util.py +0 -0
  97. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/util/test_url_util.py +0 -0
  98. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/web3/__init__.py +0 -0
  99. {kiba_core-0.5.3.dev53 → kiba_core-0.5.3.dev55}/tests/web3/test_rest_eth_client.py +0 -0
@@ -34,6 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
34
34
  - [MINOR] Added `blockNumber` to `EthClient.multicall` function
35
35
  - [MINOR] Added `SqlMessageQueue` and `CosmosMessageQueue` to support database backed queues
36
36
  - [MINOR] Added `shouldAllowFailures` to `EthClient.multicall` to allow individual calls to fail without reverting the whole batch
37
+ - [MINOR] Added `KibaException.from_headers`/`outgoing_headers` hooks so exceptions can be built from and emit response headers; used by `TooManyRequestsException` (`Retry-After`, now also parsed automatically by `Requester` from 429 responses) and `RedirectException` (`Location`/`Cache-Control`), and applied generically by `ExceptionHandlingMiddleware`
37
38
 
38
39
  ### Changed
39
40
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kiba-core
3
- Version: 0.5.3.dev53
3
+ Version: 0.5.3.dev55
4
4
  Summary: Kiba Labs' python utilities
5
5
  Project-URL: repository, https://github.com/kibalabs/core-py
6
6
  Requires-Python: <4,>=3.12
@@ -1,4 +1,5 @@
1
1
  import functools
2
+ import hmac
2
3
  import typing
3
4
  from collections.abc import AsyncIterator
4
5
 
@@ -101,7 +102,7 @@ class StaticTokenAuthorizer(TokenAuthorizer):
101
102
  self._token = token
102
103
 
103
104
  async def validate_token(self, token: str) -> None:
104
- if token != self._token:
105
+ if not hmac.compare_digest(token, self._token):
105
106
  raise ForbiddenException(message='AUTH_INVALID')
106
107
 
107
108
 
@@ -20,6 +20,7 @@ class ExceptionHandlingMiddleware(BaseHTTPMiddleware):
20
20
  @staticmethod
21
21
  def _convert_exception(exception: KibaException) -> Response:
22
22
  response = JSONResponse(status_code=exception.statusCode, content=exception.to_dict())
23
+ response.headers.update(exception.outgoing_headers())
23
24
  return response
24
25
 
25
26
  async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
@@ -27,10 +28,7 @@ class ExceptionHandlingMiddleware(BaseHTTPMiddleware):
27
28
  response = await call_next(request)
28
29
  except RedirectException as exception:
29
30
  response = Response(status_code=exception.statusCode)
30
- response.headers['Location'] = exception.location
31
- # TODO(krishan711): clean this up after major version bump (since shouldAddCacheHeader field will always exist)
32
- if not hasattr(exception, 'shouldAddCacheHeader') or exception.shouldAddCacheHeader:
33
- response.headers['Cache-Control'] = f'max-age={60 * 60 * 24 * 365}'
31
+ response.headers.update(exception.outgoing_headers())
34
32
  except ClientException as exception:
35
33
  if self.shouldSquashClientExceptions:
36
34
  logging.error(f'{exception.exceptionType} occurred: {exception.message}')
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import datetime
4
+ import email.utils
3
5
  from collections.abc import Mapping
4
6
 
5
7
  from core.util.typing_util import JsonObject
@@ -18,6 +20,15 @@ class KibaException(Exception): # noqa: N818
18
20
  return exception
19
21
  return KibaException(message=str(exception), statusCode=statusCode, exceptionType=exception.__class__.__name__)
20
22
 
23
+ @classmethod
24
+ def from_headers(cls, message: str | None, statusCode: int, headers: Mapping[str, str]) -> KibaException: # noqa: ARG003
25
+ # NOTE(krishan711): default ignores headers; subclasses that need response headers (e.g. Retry-After, Location) should override
26
+ return cls(message=message)
27
+
28
+ def outgoing_headers(self) -> Mapping[str, str]:
29
+ # NOTE(krishan711): default has no extra headers; subclasses that need to set response headers (e.g. Retry-After, Location) should override
30
+ return {}
31
+
21
32
  def to_dict(self) -> JsonObject:
22
33
  return {
23
34
  'exceptionType': self.exceptionType,
@@ -194,9 +205,34 @@ class PreconditionRequiredException(ClientException):
194
205
 
195
206
 
196
207
  class TooManyRequestsException(ClientException):
197
- def __init__(self, message: str | None = None) -> None:
208
+ def __init__(self, message: str | None = None, retryAfterSeconds: int | None = None) -> None:
198
209
  message = message or 'Too Many Requests'
199
210
  super().__init__(message=message, statusCode=429)
211
+ self.retryAfterSeconds = retryAfterSeconds
212
+
213
+ @staticmethod
214
+ def _parse_retry_after_header(value: str | None) -> int | None:
215
+ if not value:
216
+ return None
217
+ if value.strip().isdigit():
218
+ return int(value.strip())
219
+ try:
220
+ retryDate = email.utils.parsedate_to_datetime(value)
221
+ except (TypeError, ValueError):
222
+ return None
223
+ if retryDate.tzinfo is None:
224
+ retryDate = retryDate.replace(tzinfo=datetime.UTC)
225
+ remainingSeconds = int((retryDate - datetime.datetime.now(datetime.UTC)).total_seconds())
226
+ return max(0, remainingSeconds)
227
+
228
+ @classmethod
229
+ def from_headers(cls, message: str | None, statusCode: int, headers: Mapping[str, str]) -> TooManyRequestsException: # noqa: ARG003
230
+ return cls(message=message, retryAfterSeconds=cls._parse_retry_after_header(headers.get('Retry-After')))
231
+
232
+ def outgoing_headers(self) -> Mapping[str, str]:
233
+ if self.retryAfterSeconds is None:
234
+ return {}
235
+ return {'Retry-After': str(self.retryAfterSeconds)}
200
236
 
201
237
 
202
238
  class RequestHeaderFieldsTooLargeException(ClientException):
@@ -394,6 +430,12 @@ class RedirectException(KibaException):
394
430
  self.location = location
395
431
  self.shouldAddCacheHeader = shouldAddCacheHeader
396
432
 
433
+ def outgoing_headers(self) -> Mapping[str, str]:
434
+ headers = {'Location': self.location}
435
+ if self.shouldAddCacheHeader:
436
+ headers['Cache-Control'] = f'max-age={60 * 60 * 24 * 365}'
437
+ return headers
438
+
397
439
 
398
440
  class MovedPermanentlyRedirectException(RedirectException):
399
441
  def __init__(self, location: str, message: str | None = None, shouldAddCacheHeader: bool | None = True) -> None:
@@ -143,7 +143,7 @@ class Requester:
143
143
  message = httpxResponse.headers['www-authenticate']
144
144
  if HTTP_EXCEPTIONS_MAP.get(httpxResponse.status_code) is not None:
145
145
  exceptionCls = HTTP_EXCEPTIONS_MAP[httpxResponse.status_code]
146
- exception: KibaException = exceptionCls(message=message)
146
+ exception: KibaException = exceptionCls.from_headers(message=message, statusCode=httpxResponse.status_code, headers=httpxResponse.headers)
147
147
  else:
148
148
  exception = ResponseException(message=message, statusCode=httpxResponse.status_code, headers=httpxResponse.headers)
149
149
  raise exception
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kiba-core
3
- Version: 0.5.3.dev53
3
+ Version: 0.5.3.dev55
4
4
  Summary: Kiba Labs' python utilities
5
5
  Project-URL: repository, https://github.com/kibalabs/core-py
6
6
  Requires-Python: <4,>=3.12
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kiba-core"
3
- version = "0.5.3.dev53"
3
+ version = "0.5.3.dev55"
4
4
  description = "Kiba Labs' python utilities"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12, <4"