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.
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/CHANGELOG.md +3 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/PKG-INFO +14 -14
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/default_routes.py +3 -2
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/middleware/exception_handling_middleware.py +2 -2
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/requester/requester.py +1 -1
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/s3_manager.py +1 -1
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/store/database.py +3 -0
- kiba_core-0.5.3.dev57/core/util/typing_util.py +5 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/web3/eth_client.py +3 -2
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/kiba_core.egg-info/PKG-INFO +14 -14
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/kiba_core.egg-info/SOURCES.txt +6 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/kiba_core.egg-info/requires.txt +13 -13
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/pyproject.toml +14 -14
- kiba_core-0.5.3.dev57/tests/api/test_api_response.py +39 -0
- kiba_core-0.5.3.dev57/tests/api/test_exception_handling_middleware.py +109 -0
- kiba_core-0.5.3.dev57/tests/requester/test_requester.py +41 -0
- kiba_core-0.5.3.dev57/tests/test_database.py +121 -0
- kiba_core-0.5.3.dev57/tests/test_exceptions.py +71 -0
- kiba_core-0.5.3.dev57/tests/web3/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/uv.lock +1041 -862
- kiba_core-0.5.3.dev55/core/util/typing_util.py +0 -7
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/.github/pull_request_template.md +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/.github/workflows/deploy.yml +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/.github/workflows/pull-request.yml +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/.github/workflows/release.yml +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/.gitignore +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/Dockerfile +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/README.md +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/api_request.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/api_response.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/authorizer.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/health.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/json_route.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/middleware/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/middleware/database_connection_middleware.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/middleware/logging_middleware.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/middleware/server_headers_middleware.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/streaming_json_route.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/aws_requester.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/caching/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/caching/cache.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/caching/dict_cache.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/caching/file_cache.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/exceptions.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/http/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/http/basic_authentication.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/http/jwt.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/http/rest_method.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/logging.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/notifications/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/notifications/discord_client.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/notifications/notification_client.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/notifications/slack_client.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/py.typed +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/aqs.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/cosmos.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/message_queue.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/message_queue_processor.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/model.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/sql.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/queues/sqs.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/requester/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/service_client.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/store/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/store/retriever.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/store/saver.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/async_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/chain_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/date_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/dict_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/file_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/hashing_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/http_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/json_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/list_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/string_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/url_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/util/value_holder.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/web3/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/web3/multicall3.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/kiba_core.egg-info/dependency_links.txt +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/kiba_core.egg-info/top_level.txt +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/makefile +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/setup.cfg +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/api/test_authorizer.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/api/test_json_route.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/api/test_streaming_json_route.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/caching/test_dict_cache.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/caching/test_file_cache.py +0 -0
- {kiba_core-0.5.3.dev55/tests/web3 → kiba_core-0.5.3.dev57/tests/requester}/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/__init__.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_async_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_chain_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_date_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_dict_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_file_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_json_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_list_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_string_util.py +0 -0
- {kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/tests/util/test_url_util.py +0 -0
- {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.
|
|
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.
|
|
10
|
-
Requires-Dist: orjson>=3.
|
|
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.
|
|
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.
|
|
16
|
+
Requires-Dist: uvicorn[standard]>=0.53.0; extra == "core-api"
|
|
17
17
|
Provides-Extra: storage
|
|
18
|
-
Requires-Dist: aiobotocore>=3.9.
|
|
18
|
+
Requires-Dist: aiobotocore>=3.9.1; extra == "storage"
|
|
19
19
|
Provides-Extra: queues
|
|
20
|
-
Requires-Dist: aiobotocore>=3.9.
|
|
20
|
+
Requires-Dist: aiobotocore>=3.9.1; extra == "queues"
|
|
21
21
|
Provides-Extra: queue-sqs
|
|
22
|
-
Requires-Dist: aiobotocore>=3.9.
|
|
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.
|
|
26
|
+
Requires-Dist: azure-cosmos>=4.17.0; extra == "queue-cosmos"
|
|
27
27
|
Provides-Extra: database-psql
|
|
28
|
-
Requires-Dist: sqlalchemy[asyncio]>=2.0.
|
|
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.
|
|
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:
|
|
34
|
+
Requires-Dist: httpx2>=2.13.0; extra == "requester"
|
|
35
35
|
Provides-Extra: web3
|
|
36
|
-
Requires-Dist: web3>=
|
|
36
|
+
Requires-Dist: web3>=8.0.0; extra == "web3"
|
|
37
37
|
Provides-Extra: types
|
|
38
|
-
Requires-Dist: types-aiobotocore[essential]>=3.9.
|
|
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
|
|
11
|
+
return KibaJSONResponse(content={'server': name, 'version': version, 'environment': environment})
|
|
11
12
|
|
|
12
13
|
return [
|
|
13
14
|
Route('/', methods=['GET'], endpoint=root),
|
{kiba_core-0.5.3.dev55 → kiba_core-0.5.3.dev57}/core/api/middleware/exception_handling_middleware.py
RENAMED
|
@@ -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 =
|
|
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
|
|
|
@@ -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
|
|
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,
|
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
10
|
-
Requires-Dist: orjson>=3.
|
|
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.
|
|
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.
|
|
16
|
+
Requires-Dist: uvicorn[standard]>=0.53.0; extra == "core-api"
|
|
17
17
|
Provides-Extra: storage
|
|
18
|
-
Requires-Dist: aiobotocore>=3.9.
|
|
18
|
+
Requires-Dist: aiobotocore>=3.9.1; extra == "storage"
|
|
19
19
|
Provides-Extra: queues
|
|
20
|
-
Requires-Dist: aiobotocore>=3.9.
|
|
20
|
+
Requires-Dist: aiobotocore>=3.9.1; extra == "queues"
|
|
21
21
|
Provides-Extra: queue-sqs
|
|
22
|
-
Requires-Dist: aiobotocore>=3.9.
|
|
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.
|
|
26
|
+
Requires-Dist: azure-cosmos>=4.17.0; extra == "queue-cosmos"
|
|
27
27
|
Provides-Extra: database-psql
|
|
28
|
-
Requires-Dist: sqlalchemy[asyncio]>=2.0.
|
|
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.
|
|
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:
|
|
34
|
+
Requires-Dist: httpx2>=2.13.0; extra == "requester"
|
|
35
35
|
Provides-Extra: web3
|
|
36
|
-
Requires-Dist: web3>=
|
|
36
|
+
Requires-Dist: web3>=8.0.0; extra == "web3"
|
|
37
37
|
Provides-Extra: types
|
|
38
|
-
Requires-Dist: types-aiobotocore[essential]>=3.9.
|
|
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.
|
|
3
|
-
orjson>=3.
|
|
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.
|
|
7
|
+
uvicorn[standard]>=0.53.0
|
|
8
8
|
|
|
9
9
|
[core-api]
|
|
10
10
|
starlette>=1.6.0
|
|
11
|
-
uvicorn[standard]>=0.
|
|
11
|
+
uvicorn[standard]>=0.53.0
|
|
12
12
|
|
|
13
13
|
[database-psql]
|
|
14
|
-
sqlalchemy[asyncio]>=2.0.
|
|
14
|
+
sqlalchemy[asyncio]>=2.0.54
|
|
15
15
|
asyncpg>=0.31.0
|
|
16
16
|
|
|
17
17
|
[database-sqlite]
|
|
18
|
-
sqlalchemy[asyncio]>=2.0.
|
|
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.
|
|
25
|
+
azure-cosmos>=4.17.0
|
|
26
26
|
|
|
27
27
|
[queue-sqs]
|
|
28
|
-
aiobotocore>=3.9.
|
|
28
|
+
aiobotocore>=3.9.1
|
|
29
29
|
|
|
30
30
|
[queues]
|
|
31
|
-
aiobotocore>=3.9.
|
|
31
|
+
aiobotocore>=3.9.1
|
|
32
32
|
|
|
33
33
|
[requester]
|
|
34
|
-
|
|
34
|
+
httpx2>=2.13.0
|
|
35
35
|
|
|
36
36
|
[storage]
|
|
37
|
-
aiobotocore>=3.9.
|
|
37
|
+
aiobotocore>=3.9.1
|
|
38
38
|
|
|
39
39
|
[types]
|
|
40
|
-
types-aiobotocore[essential]>=3.9.
|
|
40
|
+
types-aiobotocore[essential]>=3.9.1
|
|
41
41
|
types-aiofiles>=25.1.0.20260518
|
|
42
42
|
|
|
43
43
|
[web3]
|
|
44
|
-
web3>=
|
|
44
|
+
web3>=8.0.0
|
|
@@ -1,56 +1,56 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "kiba-core"
|
|
3
|
-
version = "0.5.3.
|
|
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.
|
|
10
|
-
"orjson>=3.
|
|
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.
|
|
16
|
+
"uvicorn[standard]>=0.53.0",
|
|
17
17
|
]
|
|
18
18
|
core-api = [
|
|
19
19
|
"starlette>=1.6.0",
|
|
20
|
-
"uvicorn[standard]>=0.
|
|
20
|
+
"uvicorn[standard]>=0.53.0",
|
|
21
21
|
]
|
|
22
22
|
storage = [
|
|
23
|
-
"aiobotocore>=3.9.
|
|
23
|
+
"aiobotocore>=3.9.1",
|
|
24
24
|
]
|
|
25
25
|
# TODO(krishan711): remove this in next major update
|
|
26
26
|
queues = [
|
|
27
|
-
"aiobotocore>=3.9.
|
|
27
|
+
"aiobotocore>=3.9.1",
|
|
28
28
|
]
|
|
29
29
|
queue-sqs = [
|
|
30
|
-
"aiobotocore>=3.9.
|
|
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.
|
|
36
|
+
"azure-cosmos>=4.17.0",
|
|
37
37
|
]
|
|
38
38
|
database-psql = [
|
|
39
|
-
"sqlalchemy[asyncio]>=2.0.
|
|
39
|
+
"sqlalchemy[asyncio]>=2.0.54",
|
|
40
40
|
"asyncpg>=0.31.0",
|
|
41
41
|
]
|
|
42
42
|
database-sqlite = [
|
|
43
|
-
"sqlalchemy[asyncio]>=2.0.
|
|
43
|
+
"sqlalchemy[asyncio]>=2.0.54",
|
|
44
44
|
"aiosqlite>=0.22.1",
|
|
45
45
|
]
|
|
46
46
|
requester = [
|
|
47
|
-
"
|
|
47
|
+
"httpx2>=2.13.0",
|
|
48
48
|
]
|
|
49
49
|
web3 = [
|
|
50
|
-
"web3>=
|
|
50
|
+
"web3>=8.0.0",
|
|
51
51
|
]
|
|
52
52
|
types = [
|
|
53
|
-
"types-aiobotocore[essential]>=3.9.
|
|
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')
|