kiba-core 0.5.3.dev55__tar.gz → 0.5.3.dev57__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 (106) hide show
  1. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/CHANGELOG.md +3 -0
  2. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/PKG-INFO +14 -14
  3. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/default_routes.py +3 -2
  4. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/middleware/exception_handling_middleware.py +2 -2
  5. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/requester/requester.py +1 -1
  6. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/s3_manager.py +1 -1
  7. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/store/database.py +3 -0
  8. kiba_core-0.5.3.dev57/core/util/typing_util.py +5 -0
  9. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/web3/eth_client.py +3 -2
  10. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/kiba_core.egg-info/PKG-INFO +14 -14
  11. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/kiba_core.egg-info/SOURCES.txt +6 -0
  12. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/kiba_core.egg-info/requires.txt +13 -13
  13. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/pyproject.toml +14 -14
  14. kiba_core-0.5.3.dev57/tests/api/test_api_response.py +39 -0
  15. kiba_core-0.5.3.dev57/tests/api/test_exception_handling_middleware.py +109 -0
  16. kiba_core-0.5.3.dev57/tests/requester/test_requester.py +41 -0
  17. kiba_core-0.5.3.dev57/tests/test_database.py +121 -0
  18. kiba_core-0.5.3.dev57/tests/test_exceptions.py +71 -0
  19. kiba_core-0.5.3.dev57/tests/web3/__init__.py +0 -0
  20. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/uv.lock +1041 -862
  21. kiba_core-0.5.3.dev55/core/util/typing_util.py +0 -7
  22. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/.github/pull_request_template.md +0 -0
  23. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/.github/workflows/deploy.yml +0 -0
  24. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/.github/workflows/pull-request.yml +0 -0
  25. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/.github/workflows/release.yml +0 -0
  26. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/.gitignore +0 -0
  27. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/Dockerfile +0 -0
  28. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/README.md +0 -0
  29. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/__init__.py +0 -0
  30. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/__init__.py +0 -0
  31. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/api_request.py +0 -0
  32. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/api_response.py +0 -0
  33. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/authorizer.py +0 -0
  34. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/health.py +0 -0
  35. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/json_route.py +0 -0
  36. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/middleware/__init__.py +0 -0
  37. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/middleware/database_connection_middleware.py +0 -0
  38. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/middleware/logging_middleware.py +0 -0
  39. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/middleware/server_headers_middleware.py +0 -0
  40. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/streaming_json_route.py +0 -0
  41. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/aws_requester.py +0 -0
  42. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/caching/__init__.py +0 -0
  43. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/caching/cache.py +0 -0
  44. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/caching/dict_cache.py +0 -0
  45. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/caching/file_cache.py +0 -0
  46. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/exceptions.py +0 -0
  47. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/http/__init__.py +0 -0
  48. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/http/basic_authentication.py +0 -0
  49. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/http/jwt.py +0 -0
  50. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/http/rest_method.py +0 -0
  51. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/logging.py +0 -0
  52. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/notifications/__init__.py +0 -0
  53. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/notifications/discord_client.py +0 -0
  54. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/notifications/notification_client.py +0 -0
  55. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/notifications/slack_client.py +0 -0
  56. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/py.typed +0 -0
  57. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/__init__.py +0 -0
  58. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/aqs.py +0 -0
  59. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/cosmos.py +0 -0
  60. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/message_queue.py +0 -0
  61. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/message_queue_processor.py +0 -0
  62. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/model.py +0 -0
  63. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/sql.py +0 -0
  64. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/sqs.py +0 -0
  65. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/requester/__init__.py +0 -0
  66. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/service_client.py +0 -0
  67. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/store/__init__.py +0 -0
  68. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/store/retriever.py +0 -0
  69. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/store/saver.py +0 -0
  70. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/__init__.py +0 -0
  71. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/async_util.py +0 -0
  72. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/chain_util.py +0 -0
  73. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/date_util.py +0 -0
  74. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/dict_util.py +0 -0
  75. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/file_util.py +0 -0
  76. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/hashing_util.py +0 -0
  77. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/http_util.py +0 -0
  78. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/json_util.py +0 -0
  79. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/list_util.py +0 -0
  80. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/string_util.py +0 -0
  81. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/url_util.py +0 -0
  82. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/value_holder.py +0 -0
  83. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/web3/__init__.py +0 -0
  84. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/web3/multicall3.py +0 -0
  85. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/kiba_core.egg-info/dependency_links.txt +0 -0
  86. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/kiba_core.egg-info/top_level.txt +0 -0
  87. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/makefile +0 -0
  88. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/setup.cfg +0 -0
  89. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/__init__.py +0 -0
  90. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/api/test_authorizer.py +0 -0
  91. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/api/test_json_route.py +0 -0
  92. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/api/test_streaming_json_route.py +0 -0
  93. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/caching/test_dict_cache.py +0 -0
  94. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/caching/test_file_cache.py +0 -0
  95. {kiba_core-0.5.3.dev55/tests/web3 → kiba_core-0.5.3.dev57/tests/requester}/__init__.py +0 -0
  96. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/__init__.py +0 -0
  97. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_async_util.py +0 -0
  98. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_chain_util.py +0 -0
  99. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_date_util.py +0 -0
  100. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_dict_util.py +0 -0
  101. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_file_util.py +0 -0
  102. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_json_util.py +0 -0
  103. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_list_util.py +0 -0
  104. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_string_util.py +0 -0
  105. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_url_util.py +0 -0
  106. {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/web3/test_rest_eth_client.py +0 -0
@@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
8
8
  ## [Unreleased]
9
9
 
10
10
  ### Added
11
+ - [MAJOR] Replace `httpx` with `httpx2` for HTTP requests
12
+ - [MINOR] Use `json_util`/`orjson` for API and database JSON serialization
13
+ - [MINOR] Use Pydantic `JsonValue` for JSON type aliases
11
14
  - [MAJOR] Update web3 to 7.8.0
12
15
  - [MINOR] Added `call_function_by_name`, `send_transaction_by_name` and `wait_for_transaction_receipt` to `RestEthClient`
13
16
  - [MINOR] Updated `RestEthClient` to use `maxPriorityFeePerGas` and `maxFeePerGas` instead of `gasPrice`
@@ -1,41 +1,41 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kiba-core
3
- Version: 0.5.3.dev55
3
+ Version: 0.5.3.dev57
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
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: aiofiles>=25.1.0
9
- Requires-Dist: pydantic>=2.13.4
10
- Requires-Dist: orjson>=3.11.9
9
+ Requires-Dist: pydantic>=2.13.5
10
+ Requires-Dist: orjson>=3.12.0
11
11
  Provides-Extra: api
12
12
  Requires-Dist: fastapi>=0.141.1; extra == "api"
13
- Requires-Dist: uvicorn[standard]>=0.51.0; extra == "api"
13
+ Requires-Dist: uvicorn[standard]>=0.53.0; extra == "api"
14
14
  Provides-Extra: core-api
15
15
  Requires-Dist: starlette>=1.6.0; extra == "core-api"
16
- Requires-Dist: uvicorn[standard]>=0.51.0; extra == "core-api"
16
+ Requires-Dist: uvicorn[standard]>=0.53.0; extra == "core-api"
17
17
  Provides-Extra: storage
18
- Requires-Dist: aiobotocore>=3.9.0; extra == "storage"
18
+ Requires-Dist: aiobotocore>=3.9.1; extra == "storage"
19
19
  Provides-Extra: queues
20
- Requires-Dist: aiobotocore>=3.9.0; extra == "queues"
20
+ Requires-Dist: aiobotocore>=3.9.1; extra == "queues"
21
21
  Provides-Extra: queue-sqs
22
- Requires-Dist: aiobotocore>=3.9.0; extra == "queue-sqs"
22
+ Requires-Dist: aiobotocore>=3.9.1; extra == "queue-sqs"
23
23
  Provides-Extra: queue-aqs
24
24
  Requires-Dist: azure-storage-queue[aio]>=12.17.0; extra == "queue-aqs"
25
25
  Provides-Extra: queue-cosmos
26
- Requires-Dist: azure-cosmos>=4.16.3; extra == "queue-cosmos"
26
+ Requires-Dist: azure-cosmos>=4.17.0; extra == "queue-cosmos"
27
27
  Provides-Extra: database-psql
28
- Requires-Dist: sqlalchemy[asyncio]>=2.0.52; extra == "database-psql"
28
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.54; extra == "database-psql"
29
29
  Requires-Dist: asyncpg>=0.31.0; extra == "database-psql"
30
30
  Provides-Extra: database-sqlite
31
- Requires-Dist: sqlalchemy[asyncio]>=2.0.52; extra == "database-sqlite"
31
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.54; extra == "database-sqlite"
32
32
  Requires-Dist: aiosqlite>=0.22.1; extra == "database-sqlite"
33
33
  Provides-Extra: requester
34
- Requires-Dist: httpx>=0.28.1; extra == "requester"
34
+ Requires-Dist: httpx2>=2.13.0; extra == "requester"
35
35
  Provides-Extra: web3
36
- Requires-Dist: web3>=7.16.0; extra == "web3"
36
+ Requires-Dist: web3>=8.0.0; extra == "web3"
37
37
  Provides-Extra: types
38
- Requires-Dist: types-aiobotocore[essential]>=3.9.0; extra == "types"
38
+ Requires-Dist: types-aiobotocore[essential]>=3.9.1; extra == "types"
39
39
  Requires-Dist: types-aiofiles>=25.1.0.20260518; extra == "types"
40
40
 
41
41
  # Core-Py
@@ -1,13 +1,14 @@
1
1
  from starlette.requests import Request
2
- from starlette.responses import JSONResponse
3
2
  from starlette.responses import Response
4
3
  from starlette.routing import BaseRoute
5
4
  from starlette.routing import Route
6
5
 
6
+ from core.api.api_response import KibaJSONResponse
7
+
7
8
 
8
9
  def create_default_routes(name: str, version: str, environment: str) -> list[BaseRoute]:
9
10
  async def root(request: Request) -> Response: # noqa: ARG001
10
- return JSONResponse(content={'server': name, 'version': version, 'environment': environment})
11
+ return KibaJSONResponse(content={'server': name, 'version': version, 'environment': environment})
11
12
 
12
13
  return [
13
14
  Route('/', methods=['GET'], endpoint=root),
@@ -1,11 +1,11 @@
1
1
  from starlette.middleware.base import BaseHTTPMiddleware
2
2
  from starlette.middleware.base import RequestResponseEndpoint
3
3
  from starlette.requests import Request
4
- from starlette.responses import JSONResponse
5
4
  from starlette.responses import Response
6
5
  from starlette.types import ASGIApp
7
6
 
8
7
  from core import logging
8
+ from core.api.api_response import KibaJSONResponse
9
9
  from core.exceptions import ClientException
10
10
  from core.exceptions import KibaException
11
11
  from core.exceptions import RedirectException
@@ -19,7 +19,7 @@ class ExceptionHandlingMiddleware(BaseHTTPMiddleware):
19
19
 
20
20
  @staticmethod
21
21
  def _convert_exception(exception: KibaException) -> Response:
22
- response = JSONResponse(status_code=exception.statusCode, content=exception.to_dict())
22
+ response = KibaJSONResponse(status_code=exception.statusCode, content=exception.to_dict())
23
23
  response.headers.update(exception.outgoing_headers())
24
24
  return response
25
25
 
@@ -9,7 +9,7 @@ from typing import IO
9
9
  from typing import Any
10
10
  from typing import Union
11
11
 
12
- import httpx
12
+ import httpx2 as httpx
13
13
 
14
14
  from core import logging
15
15
  from core.exceptions import HTTP_EXCEPTIONS_MAP
@@ -11,7 +11,7 @@ from typing import Any
11
11
 
12
12
  from aiobotocore.session import get_session as get_botocore_session
13
13
  from botocore.exceptions import ClientError
14
- from httpx import Headers
14
+ from httpx2 import Headers
15
15
 
16
16
  from core import logging
17
17
  from core.exceptions import InternalServerErrorException
@@ -14,6 +14,7 @@ from sqlalchemy.sql.selectable import TypedReturnsRows
14
14
 
15
15
  from core import logging
16
16
  from core.exceptions import InternalServerErrorException
17
+ from core.util import json_util
17
18
 
18
19
  DatabaseConnection = AsyncConnection
19
20
  ResultType = TypeVar('ResultType', bound=tuple) # type: ignore[type-arg]
@@ -43,6 +44,8 @@ class Database:
43
44
  self.connectionString,
44
45
  # echo_pool=True,
45
46
  # hide_parameters=False,
47
+ json_serializer=json_util.dumps,
48
+ json_deserializer=json_util.loads,
46
49
  pool_size=poolSize,
47
50
  pool_recycle=3600,
48
51
  pool_pre_ping=True,
@@ -0,0 +1,5 @@
1
+ from pydantic import JsonValue
2
+
3
+ type Json = JsonValue
4
+ type JsonObject = dict[str, JsonValue]
5
+ type JsonList = list[JsonValue]
@@ -264,7 +264,8 @@ class Web3EthClient(EthClientInterface):
264
264
  return self.w3.eth.get_block(blockNumber, shouldHydrateTransactions)
265
265
 
266
266
  async def get_block_uncle_count(self, blockNumber: int) -> int:
267
- return typing.cast(int, self.w3.eth.get_uncle_count(blockNumber))
267
+ result = typing.cast(Any, self.w3.manager).request_blocking(method='eth_getUncleCountByBlockNumber', params=[hex(blockNumber)])
268
+ return int(result, 16)
268
269
 
269
270
  async def get_transaction_count(self, address: str) -> int:
270
271
  return self.w3.eth.get_transaction_count(chain_util.normalize_address_checksum(value=address))
@@ -374,7 +375,7 @@ class RestEthClient(EthClientInterface):
374
375
  response = await self._make_request(method='eth_getUncleCountByBlockNumber', params=[hex(blockNumber)])
375
376
  if response['result'] is None:
376
377
  raise NotFoundException
377
- return typing.cast(int, method_formatters.PYTHONIC_RESULT_FORMATTERS[RPC.eth_getUncleCountByBlockNumber](response['result']))
378
+ return int(response['result'], 16)
378
379
 
379
380
  async def get_transaction_count(self, address: str) -> int:
380
381
  response = await self._make_request(method='eth_getTransactionCount', params=[address, 'latest'])
@@ -1,41 +1,41 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kiba-core
3
- Version: 0.5.3.dev55
3
+ Version: 0.5.3.dev57
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
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: aiofiles>=25.1.0
9
- Requires-Dist: pydantic>=2.13.4
10
- Requires-Dist: orjson>=3.11.9
9
+ Requires-Dist: pydantic>=2.13.5
10
+ Requires-Dist: orjson>=3.12.0
11
11
  Provides-Extra: api
12
12
  Requires-Dist: fastapi>=0.141.1; extra == "api"
13
- Requires-Dist: uvicorn[standard]>=0.51.0; extra == "api"
13
+ Requires-Dist: uvicorn[standard]>=0.53.0; extra == "api"
14
14
  Provides-Extra: core-api
15
15
  Requires-Dist: starlette>=1.6.0; extra == "core-api"
16
- Requires-Dist: uvicorn[standard]>=0.51.0; extra == "core-api"
16
+ Requires-Dist: uvicorn[standard]>=0.53.0; extra == "core-api"
17
17
  Provides-Extra: storage
18
- Requires-Dist: aiobotocore>=3.9.0; extra == "storage"
18
+ Requires-Dist: aiobotocore>=3.9.1; extra == "storage"
19
19
  Provides-Extra: queues
20
- Requires-Dist: aiobotocore>=3.9.0; extra == "queues"
20
+ Requires-Dist: aiobotocore>=3.9.1; extra == "queues"
21
21
  Provides-Extra: queue-sqs
22
- Requires-Dist: aiobotocore>=3.9.0; extra == "queue-sqs"
22
+ Requires-Dist: aiobotocore>=3.9.1; extra == "queue-sqs"
23
23
  Provides-Extra: queue-aqs
24
24
  Requires-Dist: azure-storage-queue[aio]>=12.17.0; extra == "queue-aqs"
25
25
  Provides-Extra: queue-cosmos
26
- Requires-Dist: azure-cosmos>=4.16.3; extra == "queue-cosmos"
26
+ Requires-Dist: azure-cosmos>=4.17.0; extra == "queue-cosmos"
27
27
  Provides-Extra: database-psql
28
- Requires-Dist: sqlalchemy[asyncio]>=2.0.52; extra == "database-psql"
28
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.54; extra == "database-psql"
29
29
  Requires-Dist: asyncpg>=0.31.0; extra == "database-psql"
30
30
  Provides-Extra: database-sqlite
31
- Requires-Dist: sqlalchemy[asyncio]>=2.0.52; extra == "database-sqlite"
31
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.54; extra == "database-sqlite"
32
32
  Requires-Dist: aiosqlite>=0.22.1; extra == "database-sqlite"
33
33
  Provides-Extra: requester
34
- Requires-Dist: httpx>=0.28.1; extra == "requester"
34
+ Requires-Dist: httpx2>=2.13.0; extra == "requester"
35
35
  Provides-Extra: web3
36
- Requires-Dist: web3>=7.16.0; extra == "web3"
36
+ Requires-Dist: web3>=8.0.0; extra == "web3"
37
37
  Provides-Extra: types
38
- Requires-Dist: types-aiobotocore[essential]>=3.9.0; extra == "types"
38
+ Requires-Dist: types-aiobotocore[essential]>=3.9.1; extra == "types"
39
39
  Requires-Dist: types-aiofiles>=25.1.0.20260518; extra == "types"
40
40
 
41
41
  # Core-Py
@@ -78,11 +78,17 @@ kiba_core.egg-info/dependency_links.txt
78
78
  kiba_core.egg-info/requires.txt
79
79
  kiba_core.egg-info/top_level.txt
80
80
  tests/__init__.py
81
+ tests/test_database.py
82
+ tests/test_exceptions.py
83
+ tests/api/test_api_response.py
81
84
  tests/api/test_authorizer.py
85
+ tests/api/test_exception_handling_middleware.py
82
86
  tests/api/test_json_route.py
83
87
  tests/api/test_streaming_json_route.py
84
88
  tests/caching/test_dict_cache.py
85
89
  tests/caching/test_file_cache.py
90
+ tests/requester/__init__.py
91
+ tests/requester/test_requester.py
86
92
  tests/util/__init__.py
87
93
  tests/util/test_async_util.py
88
94
  tests/util/test_chain_util.py
@@ -1,44 +1,44 @@
1
1
  aiofiles>=25.1.0
2
- pydantic>=2.13.4
3
- orjson>=3.11.9
2
+ pydantic>=2.13.5
3
+ orjson>=3.12.0
4
4
 
5
5
  [api]
6
6
  fastapi>=0.141.1
7
- uvicorn[standard]>=0.51.0
7
+ uvicorn[standard]>=0.53.0
8
8
 
9
9
  [core-api]
10
10
  starlette>=1.6.0
11
- uvicorn[standard]>=0.51.0
11
+ uvicorn[standard]>=0.53.0
12
12
 
13
13
  [database-psql]
14
- sqlalchemy[asyncio]>=2.0.52
14
+ sqlalchemy[asyncio]>=2.0.54
15
15
  asyncpg>=0.31.0
16
16
 
17
17
  [database-sqlite]
18
- sqlalchemy[asyncio]>=2.0.52
18
+ sqlalchemy[asyncio]>=2.0.54
19
19
  aiosqlite>=0.22.1
20
20
 
21
21
  [queue-aqs]
22
22
  azure-storage-queue[aio]>=12.17.0
23
23
 
24
24
  [queue-cosmos]
25
- azure-cosmos>=4.16.3
25
+ azure-cosmos>=4.17.0
26
26
 
27
27
  [queue-sqs]
28
- aiobotocore>=3.9.0
28
+ aiobotocore>=3.9.1
29
29
 
30
30
  [queues]
31
- aiobotocore>=3.9.0
31
+ aiobotocore>=3.9.1
32
32
 
33
33
  [requester]
34
- httpx>=0.28.1
34
+ httpx2>=2.13.0
35
35
 
36
36
  [storage]
37
- aiobotocore>=3.9.0
37
+ aiobotocore>=3.9.1
38
38
 
39
39
  [types]
40
- types-aiobotocore[essential]>=3.9.0
40
+ types-aiobotocore[essential]>=3.9.1
41
41
  types-aiofiles>=25.1.0.20260518
42
42
 
43
43
  [web3]
44
- web3>=7.16.0
44
+ web3>=8.0.0
@@ -1,56 +1,56 @@
1
1
  [project]
2
2
  name = "kiba-core"
3
- version = "0.5.3.dev55"
3
+ version = "0.5.3.dev57"
4
4
  description = "Kiba Labs' python utilities"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12, <4"
7
7
  dependencies = [
8
8
  "aiofiles>=25.1.0",
9
- "pydantic>=2.13.4",
10
- "orjson>=3.11.9",
9
+ "pydantic>=2.13.5",
10
+ "orjson>=3.12.0",
11
11
  ]
12
12
 
13
13
  [project.optional-dependencies]
14
14
  api = [
15
15
  "fastapi>=0.141.1",
16
- "uvicorn[standard]>=0.51.0",
16
+ "uvicorn[standard]>=0.53.0",
17
17
  ]
18
18
  core-api = [
19
19
  "starlette>=1.6.0",
20
- "uvicorn[standard]>=0.51.0",
20
+ "uvicorn[standard]>=0.53.0",
21
21
  ]
22
22
  storage = [
23
- "aiobotocore>=3.9.0",
23
+ "aiobotocore>=3.9.1",
24
24
  ]
25
25
  # TODO(krishan711): remove this in next major update
26
26
  queues = [
27
- "aiobotocore>=3.9.0",
27
+ "aiobotocore>=3.9.1",
28
28
  ]
29
29
  queue-sqs = [
30
- "aiobotocore>=3.9.0",
30
+ "aiobotocore>=3.9.1",
31
31
  ]
32
32
  queue-aqs = [
33
33
  "azure-storage-queue[aio]>=12.17.0",
34
34
  ]
35
35
  queue-cosmos = [
36
- "azure-cosmos>=4.16.3",
36
+ "azure-cosmos>=4.17.0",
37
37
  ]
38
38
  database-psql = [
39
- "sqlalchemy[asyncio]>=2.0.52",
39
+ "sqlalchemy[asyncio]>=2.0.54",
40
40
  "asyncpg>=0.31.0",
41
41
  ]
42
42
  database-sqlite = [
43
- "sqlalchemy[asyncio]>=2.0.52",
43
+ "sqlalchemy[asyncio]>=2.0.54",
44
44
  "aiosqlite>=0.22.1",
45
45
  ]
46
46
  requester = [
47
- "httpx>=0.28.1",
47
+ "httpx2>=2.13.0",
48
48
  ]
49
49
  web3 = [
50
- "web3>=7.16.0",
50
+ "web3>=8.0.0",
51
51
  ]
52
52
  types = [
53
- "types-aiobotocore[essential]>=3.9.0",
53
+ "types-aiobotocore[essential]>=3.9.1",
54
54
  "types-aiofiles>=25.1.0.20260518",
55
55
  ]
56
56
 
@@ -0,0 +1,39 @@
1
+ import datetime
2
+
3
+ from starlette.applications import Starlette
4
+ from starlette.requests import Request
5
+ from starlette.routing import Route
6
+ from starlette.testclient import TestClient
7
+
8
+ from core.api.api_response import KibaJSONResponse
9
+ from core.api.default_routes import create_default_routes
10
+ from core.api.middleware.exception_handling_middleware import ExceptionHandlingMiddleware
11
+ from core.exceptions import BadRequestException
12
+
13
+
14
+ def test_kiba_json_response_uses_compact_utf8_serialization():
15
+ response = KibaJSONResponse(content={'message': 'Hello 🌍', 'createdAt': datetime.datetime(2025, 1, 2, 3, 4, 5), 'values': [1, 2]})
16
+
17
+ assert response.body == '{"message":"Hello 🌍","createdAt":"2025-01-02T03:04:05","values":[1,2]}'.encode()
18
+
19
+
20
+ def test_default_route_uses_kiba_json_response():
21
+ app = Starlette(routes=create_default_routes(name='core', version='1.0', environment='test'))
22
+ client = TestClient(app)
23
+
24
+ response = client.get('/')
25
+
26
+ assert response.content == b'{"server":"core","version":"1.0","environment":"test"}'
27
+
28
+
29
+ def test_exception_handling_middleware_uses_kiba_json_response():
30
+ async def failing_endpoint(_request: Request) -> KibaJSONResponse:
31
+ raise BadRequestException(message='bad input')
32
+
33
+ app = Starlette(routes=[Route('/fail', failing_endpoint)])
34
+ app.add_middleware(ExceptionHandlingMiddleware)
35
+ client = TestClient(app, raise_server_exceptions=False)
36
+
37
+ response = client.get('/fail')
38
+
39
+ assert response.content == b'{"exceptionType":"BadRequestException","message":"bad input","fields":{},"statusCode":400}'
@@ -0,0 +1,109 @@
1
+ import pytest
2
+ from pydantic import BaseModel
3
+ from starlette.applications import Starlette
4
+ from starlette.routing import Route
5
+ from starlette.testclient import TestClient
6
+
7
+ from core.api.api_request import KibaApiRequest
8
+ from core.api.json_route import json_route
9
+ from core.api.middleware.exception_handling_middleware import ExceptionHandlingMiddleware
10
+ from core.exceptions import BadRequestException
11
+ from core.exceptions import FoundRedirectException
12
+ from core.exceptions import MovedPermanentlyRedirectException
13
+ from core.exceptions import TooManyRequestsException
14
+
15
+
16
+ class SimpleRequest(BaseModel):
17
+ value: str
18
+
19
+
20
+ class SimpleResponse(BaseModel):
21
+ result: str
22
+
23
+
24
+ @pytest.fixture
25
+ def rate_limited_client():
26
+ @json_route(requestType=SimpleRequest, responseType=SimpleResponse)
27
+ async def limited_endpoint(request: KibaApiRequest[SimpleRequest]) -> SimpleResponse:
28
+ raise TooManyRequestsException(message='slow down', retryAfterSeconds=30)
29
+
30
+ app = Starlette(routes=[Route('/limited', limited_endpoint, methods=['POST'])])
31
+ app.add_middleware(ExceptionHandlingMiddleware)
32
+ return TestClient(app, raise_server_exceptions=False)
33
+
34
+
35
+ @pytest.fixture
36
+ def rate_limited_no_retry_client():
37
+ @json_route(requestType=SimpleRequest, responseType=SimpleResponse)
38
+ async def limited_endpoint(request: KibaApiRequest[SimpleRequest]) -> SimpleResponse:
39
+ raise TooManyRequestsException(message='slow down')
40
+
41
+ app = Starlette(routes=[Route('/limited', limited_endpoint, methods=['POST'])])
42
+ app.add_middleware(ExceptionHandlingMiddleware)
43
+ return TestClient(app, raise_server_exceptions=False)
44
+
45
+
46
+ @pytest.fixture
47
+ def found_redirect_client():
48
+ @json_route(requestType=SimpleRequest, responseType=SimpleResponse)
49
+ async def redirect_endpoint(request: KibaApiRequest[SimpleRequest]) -> SimpleResponse:
50
+ raise FoundRedirectException(location='https://example.com/elsewhere')
51
+
52
+ app = Starlette(routes=[Route('/redirect', redirect_endpoint, methods=['POST'])])
53
+ app.add_middleware(ExceptionHandlingMiddleware)
54
+ return TestClient(app, raise_server_exceptions=False, follow_redirects=False)
55
+
56
+
57
+ @pytest.fixture
58
+ def permanent_redirect_client():
59
+ @json_route(requestType=SimpleRequest, responseType=SimpleResponse)
60
+ async def redirect_endpoint(request: KibaApiRequest[SimpleRequest]) -> SimpleResponse:
61
+ raise MovedPermanentlyRedirectException(location='https://example.com/moved')
62
+
63
+ app = Starlette(routes=[Route('/redirect', redirect_endpoint, methods=['POST'])])
64
+ app.add_middleware(ExceptionHandlingMiddleware)
65
+ return TestClient(app, raise_server_exceptions=False, follow_redirects=False)
66
+
67
+
68
+ @pytest.fixture
69
+ def plain_error_client():
70
+ @json_route(requestType=SimpleRequest, responseType=SimpleResponse)
71
+ async def failing_endpoint(request: KibaApiRequest[SimpleRequest]) -> SimpleResponse:
72
+ raise BadRequestException(message='bad input')
73
+
74
+ app = Starlette(routes=[Route('/fail', failing_endpoint, methods=['POST'])])
75
+ app.add_middleware(ExceptionHandlingMiddleware)
76
+ return TestClient(app, raise_server_exceptions=False)
77
+
78
+
79
+ def test_too_many_requests_returns_429_with_retry_after_header(rate_limited_client):
80
+ response = rate_limited_client.post('/limited', json={'value': 'hello'})
81
+ assert response.status_code == 429
82
+ assert response.headers['retry-after'] == '30'
83
+
84
+
85
+ def test_too_many_requests_without_retry_after_omits_header(rate_limited_no_retry_client):
86
+ response = rate_limited_no_retry_client.post('/limited', json={'value': 'hello'})
87
+ assert response.status_code == 429
88
+ assert 'retry-after' not in response.headers
89
+
90
+
91
+ def test_found_redirect_sets_location_without_cache_header(found_redirect_client):
92
+ response = found_redirect_client.post('/redirect', json={'value': 'hello'})
93
+ assert response.status_code == 302
94
+ assert response.headers['location'] == 'https://example.com/elsewhere'
95
+ assert 'cache-control' not in response.headers
96
+
97
+
98
+ def test_permanent_redirect_sets_location_and_cache_header(permanent_redirect_client):
99
+ response = permanent_redirect_client.post('/redirect', json={'value': 'hello'})
100
+ assert response.status_code == 301
101
+ assert response.headers['location'] == 'https://example.com/moved'
102
+ assert response.headers['cache-control'] == f'max-age={60 * 60 * 24 * 365}'
103
+
104
+
105
+ def test_plain_exception_has_no_extra_headers(plain_error_client):
106
+ response = plain_error_client.post('/fail', json={'value': 'hello'})
107
+ assert response.status_code == 400
108
+ assert 'retry-after' not in response.headers
109
+ assert 'location' not in response.headers
@@ -0,0 +1,41 @@
1
+ import httpx2 as httpx
2
+ import pytest
3
+
4
+ from core.exceptions import NotFoundException
5
+ from core.exceptions import TooManyRequestsException
6
+ from core.requester.requester import Requester
7
+
8
+
9
+ class TestRequesterExceptionHeaders:
10
+
11
+ @pytest.mark.asyncio
12
+ async def test_429_response_populates_retry_after_from_header(self):
13
+ def handler(request: httpx.Request) -> httpx.Response: # noqa: ARG001
14
+ return httpx.Response(429, headers={'Retry-After': '17'}, text='Too Many Requests')
15
+
16
+ requester = Requester()
17
+ requester.client = httpx.AsyncClient(transport=httpx.MockTransport(handler))
18
+ with pytest.raises(TooManyRequestsException) as excInfo:
19
+ await requester.get(url='https://example.com/resource')
20
+ assert excInfo.value.retryAfterSeconds == 17
21
+
22
+ @pytest.mark.asyncio
23
+ async def test_429_response_without_retry_after_header(self):
24
+ def handler(request: httpx.Request) -> httpx.Response: # noqa: ARG001
25
+ return httpx.Response(429, text='Too Many Requests')
26
+
27
+ requester = Requester()
28
+ requester.client = httpx.AsyncClient(transport=httpx.MockTransport(handler))
29
+ with pytest.raises(TooManyRequestsException) as excInfo:
30
+ await requester.get(url='https://example.com/resource')
31
+ assert excInfo.value.retryAfterSeconds is None
32
+
33
+ @pytest.mark.asyncio
34
+ async def test_404_response_still_maps_to_correct_exception_type(self):
35
+ def handler(request: httpx.Request) -> httpx.Response: # noqa: ARG001
36
+ return httpx.Response(404, text='Not Found')
37
+
38
+ requester = Requester()
39
+ requester.client = httpx.AsyncClient(transport=httpx.MockTransport(handler))
40
+ with pytest.raises(NotFoundException):
41
+ await requester.get(url='https://example.com/resource')