kiba-core 0.5.3.dev46__tar.gz → 0.5.3.dev48__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 (97) hide show
  1. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/PKG-INFO +1 -1
  2. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/api/middleware/exception_handling_middleware.py +8 -2
  3. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/api/streaming_json_route.py +1 -1
  4. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/kiba_core.egg-info/PKG-INFO +1 -1
  5. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/kiba_core.egg-info/SOURCES.txt +1 -0
  6. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/pyproject.toml +1 -1
  7. kiba_core-0.5.3.dev48/tests/api/test_authorizer.py +154 -0
  8. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/.github/pull_request_template.md +0 -0
  9. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/.github/workflows/deploy.yml +0 -0
  10. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/.github/workflows/pull-request.yml +0 -0
  11. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/.github/workflows/release.yml +0 -0
  12. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/.gitignore +0 -0
  13. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/CHANGELOG.md +0 -0
  14. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/Dockerfile +0 -0
  15. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/README.md +0 -0
  16. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/__init__.py +0 -0
  17. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/api/__init__.py +0 -0
  18. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/api/api_request.py +0 -0
  19. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/api/api_response.py +0 -0
  20. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/api/authorizer.py +0 -0
  21. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/api/default_routes.py +0 -0
  22. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/api/health.py +0 -0
  23. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/api/json_route.py +0 -0
  24. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/api/middleware/__init__.py +0 -0
  25. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/api/middleware/database_connection_middleware.py +0 -0
  26. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/api/middleware/logging_middleware.py +0 -0
  27. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/api/middleware/server_headers_middleware.py +0 -0
  28. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/aws_requester.py +0 -0
  29. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/caching/__init__.py +0 -0
  30. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/caching/cache.py +0 -0
  31. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/caching/dict_cache.py +0 -0
  32. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/caching/file_cache.py +0 -0
  33. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/exceptions.py +0 -0
  34. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/http/__init__.py +0 -0
  35. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/http/basic_authentication.py +0 -0
  36. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/http/jwt.py +0 -0
  37. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/http/rest_method.py +0 -0
  38. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/logging.py +0 -0
  39. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/notifications/__init__.py +0 -0
  40. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/notifications/discord_client.py +0 -0
  41. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/notifications/notification_client.py +0 -0
  42. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/notifications/slack_client.py +0 -0
  43. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/py.typed +0 -0
  44. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/queues/__init__.py +0 -0
  45. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/queues/aqs.py +0 -0
  46. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/queues/message_queue.py +0 -0
  47. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/queues/message_queue_processor.py +0 -0
  48. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/queues/model.py +0 -0
  49. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/queues/sqs.py +0 -0
  50. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/requester/__init__.py +0 -0
  51. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/requester/requester.py +0 -0
  52. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/s3_manager.py +0 -0
  53. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/service_client.py +0 -0
  54. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/store/__init__.py +0 -0
  55. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/store/database.py +0 -0
  56. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/store/retriever.py +0 -0
  57. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/store/saver.py +0 -0
  58. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/util/__init__.py +0 -0
  59. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/util/async_util.py +0 -0
  60. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/util/chain_util.py +0 -0
  61. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/util/date_util.py +0 -0
  62. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/util/dict_util.py +0 -0
  63. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/util/file_util.py +0 -0
  64. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/util/hashing_util.py +0 -0
  65. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/util/http_util.py +0 -0
  66. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/util/json_util.py +0 -0
  67. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/util/list_util.py +0 -0
  68. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/util/string_util.py +0 -0
  69. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/util/typing_util.py +0 -0
  70. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/util/url_util.py +0 -0
  71. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/util/value_holder.py +0 -0
  72. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/web3/__init__.py +0 -0
  73. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/web3/eth_client.py +0 -0
  74. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/core/web3/multicall3.py +0 -0
  75. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/kiba_core.egg-info/dependency_links.txt +0 -0
  76. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/kiba_core.egg-info/requires.txt +0 -0
  77. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/kiba_core.egg-info/top_level.txt +0 -0
  78. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/makefile +0 -0
  79. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/setup.cfg +0 -0
  80. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/__init__.py +0 -0
  81. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/api/test_json_route.py +0 -0
  82. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/api/test_streaming_json_route.py +0 -0
  83. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/caching/test_dict_cache.py +0 -0
  84. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/caching/test_file_cache.py +0 -0
  85. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/util/__init__.py +0 -0
  86. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/util/test_async_util.py +0 -0
  87. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/util/test_chain_util.py +0 -0
  88. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/util/test_date_util.py +0 -0
  89. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/util/test_dict_util.py +0 -0
  90. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/util/test_file_util.py +0 -0
  91. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/util/test_json_util.py +0 -0
  92. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/util/test_list_util.py +0 -0
  93. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/util/test_string_util.py +0 -0
  94. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/util/test_url_util.py +0 -0
  95. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/web3/__init__.py +0 -0
  96. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/tests/web3/test_rest_eth_client.py +0 -0
  97. {kiba_core-0.5.3.dev46 → kiba_core-0.5.3.dev48}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kiba-core
3
- Version: 0.5.3.dev46
3
+ Version: 0.5.3.dev48
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
@@ -12,9 +12,10 @@ from core.exceptions import RedirectException
12
12
 
13
13
 
14
14
  class ExceptionHandlingMiddleware(BaseHTTPMiddleware):
15
- def __init__(self, app: ASGIApp, shouldSquashClientExceptions: bool = True) -> None:
15
+ def __init__(self, app: ASGIApp, shouldSquashClientExceptions: bool = True, shouldHideInternalErrors: bool = True) -> None:
16
16
  super().__init__(app=app)
17
17
  self.shouldSquashClientExceptions = shouldSquashClientExceptions
18
+ self.shouldHideInternalErrors = shouldHideInternalErrors
18
19
 
19
20
  @staticmethod
20
21
  def _convert_exception(exception: KibaException) -> Response:
@@ -38,8 +39,13 @@ class ExceptionHandlingMiddleware(BaseHTTPMiddleware):
38
39
  response = self._convert_exception(exception=exception)
39
40
  except KibaException as exception:
40
41
  logging.exception(exception)
42
+ if self.shouldHideInternalErrors:
43
+ exception = KibaException(message='Internal Server Error', statusCode=exception.statusCode, exceptionType=exception.exceptionType)
41
44
  response = self._convert_exception(exception=exception)
42
45
  except Exception as exception: # noqa: BLE001
43
46
  logging.exception(exception)
44
- response = self._convert_exception(exception=KibaException.from_exception(exception=exception))
47
+ kibaException = KibaException.from_exception(exception=exception)
48
+ if self.shouldHideInternalErrors:
49
+ kibaException = KibaException(message='Internal Server Error', statusCode=kibaException.statusCode)
50
+ response = self._convert_exception(exception=kibaException)
45
51
  return response
@@ -52,7 +52,7 @@ def streaming_json_route[ApiRequest: BaseModel, ApiResponse: BaseModel](
52
52
  raise BadRequestException(f'Invalid request: {validationErrorMessage}')
53
53
  kibaRequest: KibaApiRequest[ApiRequest] = KibaApiRequest(scope=receivedRequest.scope, receive=receivedRequest._receive, send=receivedRequest._send) # noqa: SLF001
54
54
  kibaRequest.data = requestParams
55
- responseGeneratorOrAwaitable = func(kibaRequest)
55
+ responseGeneratorOrAwaitable = func(request=kibaRequest)
56
56
  responseGenerator = await responseGeneratorOrAwaitable if inspect.isawaitable(responseGeneratorOrAwaitable) else responseGeneratorOrAwaitable
57
57
  wrappedGenerator = _convert_to_json_generator(typing.cast(AsyncIterator[BaseModel], responseGenerator), expectedType=typing.cast(typing.Type[BaseModel], responseType))
58
58
  # NOTE(krishan711): we set content-encoding to identity to prevent gzip from trying to process it (cos it buffers all the content)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kiba-core
3
- Version: 0.5.3.dev46
3
+ Version: 0.5.3.dev48
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
@@ -76,6 +76,7 @@ kiba_core.egg-info/dependency_links.txt
76
76
  kiba_core.egg-info/requires.txt
77
77
  kiba_core.egg-info/top_level.txt
78
78
  tests/__init__.py
79
+ tests/api/test_authorizer.py
79
80
  tests/api/test_json_route.py
80
81
  tests/api/test_streaming_json_route.py
81
82
  tests/caching/test_dict_cache.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kiba-core"
3
- version = "0.5.3.dev46"
3
+ version = "0.5.3.dev48"
4
4
  description = "Kiba Labs' python utilities"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12, <4"
@@ -0,0 +1,154 @@
1
+ import pytest
2
+ from collections.abc import AsyncIterator
3
+ from pydantic import BaseModel
4
+ from starlette.applications import Starlette
5
+ from starlette.routing import Route
6
+ from starlette.testclient import TestClient
7
+
8
+ from core.api.api_request import KibaApiRequest
9
+ from core.api.authorizer import Authorizer, authorize_bearer_jwt
10
+ from core.api.json_route import json_route
11
+ from core.api.middleware.exception_handling_middleware import ExceptionHandlingMiddleware
12
+ from core.api.streaming_json_route import streaming_json_route
13
+ from core.exceptions import ForbiddenException
14
+ from core.http.jwt import Jwt
15
+
16
+
17
+ VALID_TOKEN = 'valid-token'
18
+ VALID_USER_ID = 'user-123'
19
+
20
+
21
+ class MockAuthorizer(Authorizer):
22
+ async def validate_jwt(self, jwtString: str) -> Jwt:
23
+ if jwtString != VALID_TOKEN:
24
+ raise ForbiddenException('Invalid token')
25
+ jwt = Jwt(payloadDict={'sub': VALID_USER_ID})
26
+ jwt.userId = VALID_USER_ID # type: ignore[attr-defined]
27
+ return jwt
28
+
29
+
30
+ class SimpleRequest(BaseModel):
31
+ value: str
32
+
33
+
34
+ class SimpleResponse(BaseModel):
35
+ result: str
36
+ user_id: str | None = None
37
+
38
+
39
+ authorizer = MockAuthorizer()
40
+
41
+
42
+ @pytest.fixture
43
+ def json_client():
44
+ @json_route(requestType=SimpleRequest, responseType=SimpleResponse)
45
+ @authorize_bearer_jwt(authorizer=authorizer)
46
+ async def protected_endpoint(request: KibaApiRequest[SimpleRequest]) -> SimpleResponse:
47
+ jwt = request.authJwt
48
+ return SimpleResponse(result=request.data.value, user_id=getattr(jwt, 'userId', None))
49
+
50
+ app = Starlette(routes=[Route('/protected', protected_endpoint, methods=['POST'])])
51
+ app.add_middleware(ExceptionHandlingMiddleware)
52
+ return TestClient(app, raise_server_exceptions=False)
53
+
54
+
55
+ @pytest.fixture
56
+ def streaming_client():
57
+ @streaming_json_route(requestType=SimpleRequest, responseType=SimpleResponse)
58
+ @authorize_bearer_jwt(authorizer=authorizer)
59
+ async def protected_streaming_endpoint(request: KibaApiRequest[SimpleRequest]) -> AsyncIterator[SimpleResponse]:
60
+ jwt = request.authJwt
61
+ yield SimpleResponse(result=request.data.value, user_id=getattr(jwt, 'userId', None))
62
+
63
+ app = Starlette(routes=[Route('/protected-stream', protected_streaming_endpoint, methods=['POST'])])
64
+ app.add_middleware(ExceptionHandlingMiddleware)
65
+ return TestClient(app, raise_server_exceptions=False)
66
+
67
+
68
+ # --- json_route + authorize_bearer_jwt ---
69
+
70
+ def test_json_no_auth_header_returns_403(json_client):
71
+ response = json_client.post('/protected', json={'value': 'hello'})
72
+ assert response.status_code == 403
73
+
74
+
75
+ def test_json_malformed_auth_header_not_bearer_returns_403(json_client):
76
+ response = json_client.post('/protected', json={'value': 'hello'}, headers={'Authorization': 'Basic some-creds'})
77
+ assert response.status_code == 403
78
+
79
+
80
+ def test_json_bearer_with_no_token_returns_403(json_client):
81
+ response = json_client.post('/protected', json={'value': 'hello'}, headers={'Authorization': 'Bearer '})
82
+ assert response.status_code == 403
83
+
84
+
85
+ def test_json_invalid_token_returns_403(json_client):
86
+ response = json_client.post('/protected', json={'value': 'hello'}, headers={'Authorization': 'Bearer bad-token'})
87
+ assert response.status_code == 403
88
+
89
+
90
+ def test_json_valid_token_returns_200(json_client):
91
+ response = json_client.post('/protected', json={'value': 'hello'}, headers={'Authorization': f'Bearer {VALID_TOKEN}'})
92
+ assert response.status_code == 200
93
+ assert response.json()['result'] == 'hello'
94
+
95
+
96
+ def test_json_valid_token_sets_auth_jwt_on_request(json_client):
97
+ response = json_client.post('/protected', json={'value': 'hello'}, headers={'Authorization': f'Bearer {VALID_TOKEN}'})
98
+ assert response.status_code == 200
99
+ assert response.json()['user_id'] == VALID_USER_ID
100
+
101
+
102
+ def test_json_missing_request_body_field_returns_400(json_client):
103
+ response = json_client.post('/protected', json={}, headers={'Authorization': f'Bearer {VALID_TOKEN}'})
104
+ assert response.status_code == 400
105
+
106
+
107
+ # --- streaming_json_route + authorize_bearer_jwt ---
108
+
109
+ def test_streaming_no_auth_header_returns_403(streaming_client):
110
+ response = streaming_client.post('/protected-stream', json={'value': 'hello'})
111
+ assert response.status_code == 403
112
+
113
+
114
+ def test_streaming_malformed_auth_header_not_bearer_returns_403(streaming_client):
115
+ response = streaming_client.post('/protected-stream', json={'value': 'hello'}, headers={'Authorization': 'Basic some-creds'})
116
+ assert response.status_code == 403
117
+
118
+
119
+ def test_streaming_bearer_with_no_token_returns_403(streaming_client):
120
+ response = streaming_client.post('/protected-stream', json={'value': 'hello'}, headers={'Authorization': 'Bearer '})
121
+ assert response.status_code == 403
122
+
123
+
124
+ def test_streaming_invalid_token_returns_403(streaming_client):
125
+ response = streaming_client.post('/protected-stream', json={'value': 'hello'}, headers={'Authorization': 'Bearer bad-token'})
126
+ assert response.status_code == 403
127
+
128
+
129
+ def test_streaming_valid_token_returns_200(streaming_client):
130
+ response = streaming_client.post('/protected-stream', json={'value': 'hello'}, headers={'Authorization': f'Bearer {VALID_TOKEN}'})
131
+ assert response.status_code == 200
132
+
133
+
134
+ def test_streaming_valid_token_streams_correct_data(streaming_client):
135
+ response = streaming_client.post('/protected-stream', json={'value': 'hello'}, headers={'Authorization': f'Bearer {VALID_TOKEN}'})
136
+ assert response.status_code == 200
137
+ import json
138
+ lines = [l for l in response.content.decode().strip().split('\n') if l]
139
+ assert len(lines) == 1
140
+ data = json.loads(lines[0])
141
+ assert data['result'] == 'hello'
142
+
143
+
144
+ def test_streaming_valid_token_sets_auth_jwt_on_request(streaming_client):
145
+ response = streaming_client.post('/protected-stream', json={'value': 'hello'}, headers={'Authorization': f'Bearer {VALID_TOKEN}'})
146
+ assert response.status_code == 200
147
+ import json
148
+ data = json.loads(response.content.decode().strip())
149
+ assert data['user_id'] == VALID_USER_ID
150
+
151
+
152
+ def test_streaming_missing_request_body_field_returns_400(streaming_client):
153
+ response = streaming_client.post('/protected-stream', json={}, headers={'Authorization': f'Bearer {VALID_TOKEN}'})
154
+ assert response.status_code == 400
File without changes