kiba-core 0.5.3.dev91__tar.gz → 0.5.3.dev93__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 (146) hide show
  1. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/CHANGELOG.md +1 -0
  2. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/Dockerfile +1 -1
  3. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/PKG-INFO +10 -10
  4. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/sql.py +21 -6
  5. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/kiba_core.egg-info/PKG-INFO +10 -10
  6. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/kiba_core.egg-info/requires.txt +9 -9
  7. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/pyproject.toml +11 -11
  8. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/queues/test_sql_queue.py +27 -0
  9. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/uv.lock +354 -354
  10. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/.github/pull_request_template.md +0 -0
  11. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/.github/workflows/deploy.yml +0 -0
  12. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/.github/workflows/publish.yml +0 -0
  13. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/.github/workflows/pull-request.yml +0 -0
  14. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/.github/workflows/release.yml +0 -0
  15. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/.gitignore +0 -0
  16. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/README.md +0 -0
  17. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/__init__.py +0 -0
  18. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/__init__.py +0 -0
  19. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/api_request.py +0 -0
  20. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/api_response.py +0 -0
  21. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/authorizer.py +0 -0
  22. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/default_routes.py +0 -0
  23. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/health.py +0 -0
  24. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/json_route.py +0 -0
  25. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/middleware/__init__.py +0 -0
  26. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/middleware/database_connection_middleware.py +0 -0
  27. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/middleware/exception_handling_middleware.py +0 -0
  28. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/middleware/logging_middleware.py +0 -0
  29. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/middleware/origin_ip_middleware.py +0 -0
  30. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/middleware/server_headers_middleware.py +0 -0
  31. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/openapi.py +0 -0
  32. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/rate_limit.py +0 -0
  33. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/request_body.py +0 -0
  34. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/request_context.py +0 -0
  35. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/route.py +0 -0
  36. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/route_auth.py +0 -0
  37. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/route_metadata.py +0 -0
  38. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/streaming_json_route.py +0 -0
  39. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/uploaded_file.py +0 -0
  40. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/aws_requester.py +0 -0
  41. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/caching/__init__.py +0 -0
  42. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/caching/cache.py +0 -0
  43. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/caching/dict_cache.py +0 -0
  44. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/caching/file_cache.py +0 -0
  45. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/cron/__init__.py +0 -0
  46. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/cron/api_request_cron_job_processor.py +0 -0
  47. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/cron/cron_executor.py +0 -0
  48. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/cron/cron_job_processor.py +0 -0
  49. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/cron/model.py +0 -0
  50. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/exceptions.py +0 -0
  51. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/http/__init__.py +0 -0
  52. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/http/basic_authentication.py +0 -0
  53. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/http/jwt.py +0 -0
  54. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/http/rest_method.py +0 -0
  55. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/locks/__init__.py +0 -0
  56. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/locks/lock.py +0 -0
  57. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/locks/model.py +0 -0
  58. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/locks/sql.py +0 -0
  59. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/logging.py +0 -0
  60. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/notifications/__init__.py +0 -0
  61. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/notifications/discord_client.py +0 -0
  62. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/notifications/notification_client.py +0 -0
  63. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/notifications/slack_client.py +0 -0
  64. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/py.typed +0 -0
  65. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/__init__.py +0 -0
  66. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/aqs.py +0 -0
  67. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/cosmos.py +0 -0
  68. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/message_queue.py +0 -0
  69. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/message_queue_processor.py +0 -0
  70. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/model.py +0 -0
  71. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/sqs.py +0 -0
  72. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/requester/__init__.py +0 -0
  73. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/requester/requester.py +0 -0
  74. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/s3_manager.py +0 -0
  75. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/service_client.py +0 -0
  76. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/store/__init__.py +0 -0
  77. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/store/database.py +0 -0
  78. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/store/entity_repository.py +0 -0
  79. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/store/retriever.py +0 -0
  80. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/store/saver.py +0 -0
  81. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/__init__.py +0 -0
  82. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/async_util.py +0 -0
  83. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/chain_util.py +0 -0
  84. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/date_util.py +0 -0
  85. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/dict_util.py +0 -0
  86. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/file_util.py +0 -0
  87. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/hashing_util.py +0 -0
  88. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/http_util.py +0 -0
  89. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/json_util.py +0 -0
  90. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/list_util.py +0 -0
  91. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/process_util.py +0 -0
  92. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/string_util.py +0 -0
  93. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/typing_util.py +0 -0
  94. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/url_util.py +0 -0
  95. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/value_holder.py +0 -0
  96. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/web3/__init__.py +0 -0
  97. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/web3/eth_client.py +0 -0
  98. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/web3/eth_client_registry.py +0 -0
  99. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/web3/multicall3.py +0 -0
  100. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/kiba_core.egg-info/SOURCES.txt +0 -0
  101. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/kiba_core.egg-info/dependency_links.txt +0 -0
  102. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/kiba_core.egg-info/top_level.txt +0 -0
  103. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/makefile +0 -0
  104. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/setup.cfg +0 -0
  105. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/__init__.py +0 -0
  106. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_api_response.py +0 -0
  107. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_authorizer.py +0 -0
  108. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_database_connection_middleware.py +0 -0
  109. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_exception_handling_middleware.py +0 -0
  110. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_json_route.py +0 -0
  111. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_openapi.py +0 -0
  112. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_origin_ip_middleware.py +0 -0
  113. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_rate_limit.py +0 -0
  114. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_request_context.py +0 -0
  115. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_route.py +0 -0
  116. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_streaming_json_route.py +0 -0
  117. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/caching/test_dict_cache.py +0 -0
  118. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/caching/test_file_cache.py +0 -0
  119. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/cron/__init__.py +0 -0
  120. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/cron/test_cron_executor.py +0 -0
  121. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/locks/test_sql_lock.py +0 -0
  122. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/queues/conftest.py +0 -0
  123. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/queues/test_aqs_queue.py +0 -0
  124. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/queues/test_cosmos_queue.py +0 -0
  125. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/queues/test_message_queue_processor.py +0 -0
  126. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/queues/test_sqs_queue.py +0 -0
  127. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/requester/__init__.py +0 -0
  128. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/requester/test_requester.py +0 -0
  129. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/test_database.py +0 -0
  130. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/test_entity_repository.py +0 -0
  131. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/test_exceptions.py +0 -0
  132. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/test_logging.py +0 -0
  133. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/__init__.py +0 -0
  134. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_async_util.py +0 -0
  135. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_chain_util.py +0 -0
  136. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_date_util.py +0 -0
  137. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_dict_util.py +0 -0
  138. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_file_util.py +0 -0
  139. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_json_util.py +0 -0
  140. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_list_util.py +0 -0
  141. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_process_util.py +0 -0
  142. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_string_util.py +0 -0
  143. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_url_util.py +0 -0
  144. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/web3/__init__.py +0 -0
  145. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/web3/test_eth_client_registry.py +0 -0
  146. {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/web3/test_rest_eth_client.py +0 -0
@@ -8,6 +8,7 @@ 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] Added `priority` to `SqlMessageQueue.send_message` and `send_messages` (default 0): visible messages are claimed highest priority first, then by `visibleDate`, and a duplicate send, or a retry folded into a pending duplicate, keeps the higher priority. `tbl_queue_messages` has a new `priority` column (`INTEGER NOT NULL DEFAULT 0`), so apps must add it with a migration before upgrading
11
12
  - [PATCH] Fixed `DatabaseConnectionMiddleware`'s commit when the response starts failing ("another operation is in progress") for streaming responses behind a `BaseHTTPMiddleware`: `Database.execute` and `Database.commit_context_connection` now wait for each other on the same connection
12
13
  - [MINOR] Added `Database.commit_context_connection` to commit the context connection's writes so far; anything run on it afterwards is committed when the context connection ends
13
14
  - [PATCH] Updated `DatabaseConnectionMiddleware` to commit before the response starts, so a client can't get the response and make its next request before the request's writes are visible (writes made while streaming are still committed when the request ends)
@@ -1,4 +1,4 @@
1
- FROM python:3.14.8-slim
1
+ FROM python:3.14.9-slim
2
2
 
3
3
  RUN apt-get update && apt-get install --yes --no-install-recommends make git
4
4
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kiba-core
3
- Version: 0.5.3.dev91
3
+ Version: 0.5.3.dev93
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
@@ -9,36 +9,36 @@ Requires-Dist: aiofiles>=25.1.0
9
9
  Requires-Dist: pydantic>=2.14.1
10
10
  Requires-Dist: orjson>=3.13.0
11
11
  Provides-Extra: api
12
- Requires-Dist: fastapi>=0.146.0; extra == "api"
12
+ Requires-Dist: fastapi>=0.162.0; extra == "api"
13
13
  Requires-Dist: uvicorn[standard]>=0.54.0; extra == "api"
14
14
  Provides-Extra: core-api
15
- Requires-Dist: starlette>=1.9.0; extra == "core-api"
15
+ Requires-Dist: starlette>=1.10.0; extra == "core-api"
16
16
  Requires-Dist: uvicorn[standard]>=0.54.0; extra == "core-api"
17
17
  Requires-Dist: python-multipart>=0.0.32; extra == "core-api"
18
18
  Provides-Extra: storage
19
- Requires-Dist: aiobotocore>=3.9.2; extra == "storage"
19
+ Requires-Dist: aiobotocore>=3.10.0; extra == "storage"
20
20
  Provides-Extra: queues
21
- Requires-Dist: aiobotocore>=3.9.2; extra == "queues"
21
+ Requires-Dist: aiobotocore>=3.10.0; extra == "queues"
22
22
  Provides-Extra: queue-sqs
23
- Requires-Dist: aiobotocore>=3.9.2; extra == "queue-sqs"
23
+ Requires-Dist: aiobotocore>=3.10.0; extra == "queue-sqs"
24
24
  Provides-Extra: queue-aqs
25
25
  Requires-Dist: azure-storage-queue[aio]>=12.18.0; extra == "queue-aqs"
26
26
  Provides-Extra: queue-cosmos
27
27
  Requires-Dist: azure-cosmos>=4.17.1; extra == "queue-cosmos"
28
28
  Provides-Extra: database-psql
29
- Requires-Dist: sqlalchemy[asyncio]>=2.1.5; extra == "database-psql"
29
+ Requires-Dist: sqlalchemy[asyncio]>=2.1.6; extra == "database-psql"
30
30
  Requires-Dist: asyncpg>=0.32.0; extra == "database-psql"
31
31
  Provides-Extra: database-sqlite
32
- Requires-Dist: sqlalchemy[asyncio]>=2.1.5; extra == "database-sqlite"
32
+ Requires-Dist: sqlalchemy[asyncio]>=2.1.6; extra == "database-sqlite"
33
33
  Requires-Dist: aiosqlite>=0.22.1; extra == "database-sqlite"
34
34
  Provides-Extra: requester
35
- Requires-Dist: httpx2>=2.14.0; extra == "requester"
35
+ Requires-Dist: httpx2>=2.16.0; extra == "requester"
36
36
  Provides-Extra: web3
37
37
  Requires-Dist: web3<8.0.0,>=7.16.0; extra == "web3"
38
38
  Provides-Extra: cron
39
39
  Requires-Dist: apscheduler<4,>=3.11.3; extra == "cron"
40
40
  Provides-Extra: types
41
- Requires-Dist: types-aiobotocore[essential]>=3.9.2; extra == "types"
41
+ Requires-Dist: types-aiobotocore[essential]>=3.10.0; extra == "types"
42
42
  Requires-Dist: types-aiofiles>=25.1.0.20260518; extra == "types"
43
43
 
44
44
  # Core-Py
@@ -52,6 +52,7 @@ QueueMessagesTable = sqlalchemy.Table(
52
52
  sqlalchemy.Column(key='startedDate', name='started_date', type_=sqlalchemy.DateTime(timezone=True), nullable=True),
53
53
  sqlalchemy.Column(key='completedDate', name='completed_date', type_=sqlalchemy.DateTime(timezone=True), nullable=True),
54
54
  sqlalchemy.Column(key='lastError', name='last_error', type_=sqlalchemy.Text, nullable=True),
55
+ sqlalchemy.Column(key='priority', name='priority', type_=sqlalchemy.Integer, nullable=False, server_default='0'),
55
56
  sqlalchemy.Index('tbl_queue_messages_idx_queue_name_status_visible_date', 'queueName', 'status', 'visibleDate'),
56
57
  sqlalchemy.Index('tbl_queue_messages_ux_queue_name_deduplication_id_pending', 'queueName', 'deduplicationId', unique=True, postgresql_where=_PENDING_PREDICATE, sqlite_where=_PENDING_PREDICATE),
57
58
  )
@@ -109,10 +110,10 @@ class SqlMessageQueue(MessageQueue[SqlMessage]):
109
110
  return sqlalchemy_sqlite.insert(self.table)
110
111
  raise InternalServerErrorException(message=f'SqlMessageQueue does not support dialect: {dialectName}')
111
112
 
112
- async def send_message(self, message: Message, delaySeconds: int = 0) -> None:
113
- await self.send_messages(messages=[message], delaySeconds=delaySeconds)
113
+ async def send_message(self, message: Message, delaySeconds: int = 0, priority: int = 0) -> None:
114
+ await self.send_messages(messages=[message], delaySeconds=delaySeconds, priority=priority)
114
115
 
115
- async def send_messages(self, messages: Sequence[Message], delaySeconds: int = 0) -> None:
116
+ async def send_messages(self, messages: Sequence[Message], delaySeconds: int = 0, priority: int = 0) -> None:
116
117
  if not messages:
117
118
  return
118
119
  now = self.database.now()
@@ -136,13 +137,17 @@ class SqlMessageQueue(MessageQueue[SqlMessage]):
136
137
  'status': MESSAGE_STATUS_PENDING,
137
138
  'visibleDate': self.database.now(seconds=delaySeconds),
138
139
  'createdDate': now,
140
+ 'priority': priority,
139
141
  }
140
142
  )
141
143
  async with self.database.create_transaction() as connection:
142
144
  insertQuery = self._create_insert(dialectName=connection.dialect.name).values(rows)
143
145
  earliest = sqlalchemy.func.least if connection.dialect.name == 'postgresql' else sqlalchemy.func.min
146
+ highest = sqlalchemy.func.greatest if connection.dialect.name == 'postgresql' else sqlalchemy.func.max
144
147
  upsertQuery = insertQuery.on_conflict_do_update(
145
- index_elements=[self.table.c.queueName, self.table.c.deduplicationId], index_where=_PENDING_PREDICATE, set_={self.table.c.visibleDate: earliest(self.table.c.visibleDate, insertQuery.excluded.visibleDate)}
148
+ index_elements=[self.table.c.queueName, self.table.c.deduplicationId],
149
+ index_where=_PENDING_PREDICATE,
150
+ set_={self.table.c.visibleDate: earliest(self.table.c.visibleDate, insertQuery.excluded.visibleDate), self.table.c.priority: highest(self.table.c.priority, insertQuery.excluded.priority)},
146
151
  )
147
152
  await self.database.execute(query=upsertQuery, connection=connection)
148
153
 
@@ -175,7 +180,7 @@ class SqlMessageQueue(MessageQueue[SqlMessage]):
175
180
  .where(self.table.c.queueName == self.queueName)
176
181
  .where(self.table.c.status.in_([MESSAGE_STATUS_PENDING, MESSAGE_STATUS_RUNNING]))
177
182
  .where(self.table.c.visibleDate <= now)
178
- .order_by(self.table.c.visibleDate, self.table.c.id)
183
+ .order_by(self.table.c.priority.desc(), self.table.c.visibleDate, self.table.c.id)
179
184
  .limit(limit)
180
185
  .with_for_update(skip_locked=True)
181
186
  )
@@ -194,7 +199,7 @@ class SqlMessageQueue(MessageQueue[SqlMessage]):
194
199
  )
195
200
  result = await self.database.execute(query=claimQuery, connection=connection)
196
201
  rows = result.mappings().all()
197
- return sorted((SqlMessage.from_row(row=row, table=self.table) for row in rows), key=lambda message: message.id)
202
+ return [SqlMessage.from_row(row=row, table=self.table) for row in sorted(rows, key=lambda row: (-row[self.table.c.priority], row[self.table.c.id]))]
198
203
 
199
204
  def _owned_update(self, message: SqlMessage) -> sqlalchemy.Update:
200
205
  return sqlalchemy.update(self.table).where(self.table.c.id == message.id).where(self.table.c.lockToken == message.lockToken).where(self.table.c.status == MESSAGE_STATUS_RUNNING)
@@ -237,6 +242,16 @@ class SqlMessageQueue(MessageQueue[SqlMessage]):
237
242
  await self.database.execute(query=query, connection=connection)
238
243
  except sqlalchemy.exc.IntegrityError:
239
244
  # NOTE(krishan711): an identical message was enqueued while this one ran, so it will do the work instead
245
+ highest = sqlalchemy.func.greatest if connection.dialect.name == 'postgresql' else sqlalchemy.func.max
246
+ runningPriority = sqlalchemy.select(self.table.c.priority).where(self.table.c.id == message.id).scalar_subquery()
247
+ duplicateQuery = (
248
+ sqlalchemy.update(self.table)
249
+ .where(self.table.c.queueName == self.queueName)
250
+ .where(self.table.c.deduplicationId == message.deduplicationId)
251
+ .where(self.table.c.status == MESSAGE_STATUS_PENDING)
252
+ .values(priority=highest(self.table.c.priority, runningPriority))
253
+ )
254
+ await self.database.execute(query=duplicateQuery, connection=connection)
240
255
  query = self._owned_update(message=message).values(status=MESSAGE_STATUS_DEDUPLICATED, lockToken=None, completedDate=self.database.now())
241
256
  await self.database.execute(query=query, connection=connection)
242
257
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kiba-core
3
- Version: 0.5.3.dev91
3
+ Version: 0.5.3.dev93
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
@@ -9,36 +9,36 @@ Requires-Dist: aiofiles>=25.1.0
9
9
  Requires-Dist: pydantic>=2.14.1
10
10
  Requires-Dist: orjson>=3.13.0
11
11
  Provides-Extra: api
12
- Requires-Dist: fastapi>=0.146.0; extra == "api"
12
+ Requires-Dist: fastapi>=0.162.0; extra == "api"
13
13
  Requires-Dist: uvicorn[standard]>=0.54.0; extra == "api"
14
14
  Provides-Extra: core-api
15
- Requires-Dist: starlette>=1.9.0; extra == "core-api"
15
+ Requires-Dist: starlette>=1.10.0; extra == "core-api"
16
16
  Requires-Dist: uvicorn[standard]>=0.54.0; extra == "core-api"
17
17
  Requires-Dist: python-multipart>=0.0.32; extra == "core-api"
18
18
  Provides-Extra: storage
19
- Requires-Dist: aiobotocore>=3.9.2; extra == "storage"
19
+ Requires-Dist: aiobotocore>=3.10.0; extra == "storage"
20
20
  Provides-Extra: queues
21
- Requires-Dist: aiobotocore>=3.9.2; extra == "queues"
21
+ Requires-Dist: aiobotocore>=3.10.0; extra == "queues"
22
22
  Provides-Extra: queue-sqs
23
- Requires-Dist: aiobotocore>=3.9.2; extra == "queue-sqs"
23
+ Requires-Dist: aiobotocore>=3.10.0; extra == "queue-sqs"
24
24
  Provides-Extra: queue-aqs
25
25
  Requires-Dist: azure-storage-queue[aio]>=12.18.0; extra == "queue-aqs"
26
26
  Provides-Extra: queue-cosmos
27
27
  Requires-Dist: azure-cosmos>=4.17.1; extra == "queue-cosmos"
28
28
  Provides-Extra: database-psql
29
- Requires-Dist: sqlalchemy[asyncio]>=2.1.5; extra == "database-psql"
29
+ Requires-Dist: sqlalchemy[asyncio]>=2.1.6; extra == "database-psql"
30
30
  Requires-Dist: asyncpg>=0.32.0; extra == "database-psql"
31
31
  Provides-Extra: database-sqlite
32
- Requires-Dist: sqlalchemy[asyncio]>=2.1.5; extra == "database-sqlite"
32
+ Requires-Dist: sqlalchemy[asyncio]>=2.1.6; extra == "database-sqlite"
33
33
  Requires-Dist: aiosqlite>=0.22.1; extra == "database-sqlite"
34
34
  Provides-Extra: requester
35
- Requires-Dist: httpx2>=2.14.0; extra == "requester"
35
+ Requires-Dist: httpx2>=2.16.0; extra == "requester"
36
36
  Provides-Extra: web3
37
37
  Requires-Dist: web3<8.0.0,>=7.16.0; extra == "web3"
38
38
  Provides-Extra: cron
39
39
  Requires-Dist: apscheduler<4,>=3.11.3; extra == "cron"
40
40
  Provides-Extra: types
41
- Requires-Dist: types-aiobotocore[essential]>=3.9.2; extra == "types"
41
+ Requires-Dist: types-aiobotocore[essential]>=3.10.0; extra == "types"
42
42
  Requires-Dist: types-aiofiles>=25.1.0.20260518; extra == "types"
43
43
 
44
44
  # Core-Py
@@ -3,11 +3,11 @@ pydantic>=2.14.1
3
3
  orjson>=3.13.0
4
4
 
5
5
  [api]
6
- fastapi>=0.146.0
6
+ fastapi>=0.162.0
7
7
  uvicorn[standard]>=0.54.0
8
8
 
9
9
  [core-api]
10
- starlette>=1.9.0
10
+ starlette>=1.10.0
11
11
  uvicorn[standard]>=0.54.0
12
12
  python-multipart>=0.0.32
13
13
 
@@ -15,11 +15,11 @@ python-multipart>=0.0.32
15
15
  apscheduler<4,>=3.11.3
16
16
 
17
17
  [database-psql]
18
- sqlalchemy[asyncio]>=2.1.5
18
+ sqlalchemy[asyncio]>=2.1.6
19
19
  asyncpg>=0.32.0
20
20
 
21
21
  [database-sqlite]
22
- sqlalchemy[asyncio]>=2.1.5
22
+ sqlalchemy[asyncio]>=2.1.6
23
23
  aiosqlite>=0.22.1
24
24
 
25
25
  [queue-aqs]
@@ -29,19 +29,19 @@ azure-storage-queue[aio]>=12.18.0
29
29
  azure-cosmos>=4.17.1
30
30
 
31
31
  [queue-sqs]
32
- aiobotocore>=3.9.2
32
+ aiobotocore>=3.10.0
33
33
 
34
34
  [queues]
35
- aiobotocore>=3.9.2
35
+ aiobotocore>=3.10.0
36
36
 
37
37
  [requester]
38
- httpx2>=2.14.0
38
+ httpx2>=2.16.0
39
39
 
40
40
  [storage]
41
- aiobotocore>=3.9.2
41
+ aiobotocore>=3.10.0
42
42
 
43
43
  [types]
44
- types-aiobotocore[essential]>=3.9.2
44
+ types-aiobotocore[essential]>=3.10.0
45
45
  types-aiofiles>=25.1.0.20260518
46
46
 
47
47
  [web3]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kiba-core"
3
- version = "0.5.3.dev91"
3
+ version = "0.5.3.dev93"
4
4
  description = "Kiba Labs' python utilities"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12, <4"
@@ -12,23 +12,23 @@ dependencies = [
12
12
 
13
13
  [project.optional-dependencies]
14
14
  api = [
15
- "fastapi>=0.146.0",
15
+ "fastapi>=0.162.0",
16
16
  "uvicorn[standard]>=0.54.0",
17
17
  ]
18
18
  core-api = [
19
- "starlette>=1.9.0",
19
+ "starlette>=1.10.0",
20
20
  "uvicorn[standard]>=0.54.0",
21
21
  "python-multipart>=0.0.32",
22
22
  ]
23
23
  storage = [
24
- "aiobotocore>=3.9.2",
24
+ "aiobotocore>=3.10.0",
25
25
  ]
26
26
  # TODO(krishan711): remove this in next major update
27
27
  queues = [
28
- "aiobotocore>=3.9.2",
28
+ "aiobotocore>=3.10.0",
29
29
  ]
30
30
  queue-sqs = [
31
- "aiobotocore>=3.9.2",
31
+ "aiobotocore>=3.10.0",
32
32
  ]
33
33
  queue-aqs = [
34
34
  "azure-storage-queue[aio]>=12.18.0",
@@ -37,15 +37,15 @@ queue-cosmos = [
37
37
  "azure-cosmos>=4.17.1",
38
38
  ]
39
39
  database-psql = [
40
- "sqlalchemy[asyncio]>=2.1.5",
40
+ "sqlalchemy[asyncio]>=2.1.6",
41
41
  "asyncpg>=0.32.0",
42
42
  ]
43
43
  database-sqlite = [
44
- "sqlalchemy[asyncio]>=2.1.5",
44
+ "sqlalchemy[asyncio]>=2.1.6",
45
45
  "aiosqlite>=0.22.1",
46
46
  ]
47
47
  requester = [
48
- "httpx2>=2.14.0",
48
+ "httpx2>=2.16.0",
49
49
  ]
50
50
  web3 = [
51
51
  "web3>=7.16.0,<8.0.0",
@@ -54,13 +54,13 @@ cron = [
54
54
  "apscheduler>=3.11.3,<4",
55
55
  ]
56
56
  types = [
57
- "types-aiobotocore[essential]>=3.9.2",
57
+ "types-aiobotocore[essential]>=3.10.0",
58
58
  "types-aiofiles>=25.1.0.20260518",
59
59
  ]
60
60
 
61
61
  [dependency-groups]
62
62
  dev = [
63
- "kiba-build==0.1.11.dev20",
63
+ "kiba-build==0.1.11.dev22",
64
64
  ]
65
65
 
66
66
  [project.urls]
@@ -116,6 +116,24 @@ async def test_duplicate_send_keeps_the_earliest_visibility(workers: tuple[SqlMe
116
116
  assert await workerA.get_message() is not None
117
117
 
118
118
 
119
+ async def test_higher_priority_messages_are_claimed_before_older_ones(workers: tuple[SqlMessageQueue, SqlMessageQueue]):
120
+ workerA, _ = workers
121
+ await workerA.send_messages(messages=[_message(deduplicationId=None, userId=str(index)) for index in range(3)])
122
+ await workerA.send_message(message=_message(deduplicationId=None, userId='urgent'), priority=1)
123
+ messages = await workerA.get_messages(limit=2)
124
+ assert [message.content['userId'] for message in messages] == ['urgent', '0']
125
+
126
+
127
+ async def test_duplicate_send_keeps_the_highest_priority(workers: tuple[SqlMessageQueue, SqlMessageQueue]):
128
+ workerA, _ = workers
129
+ await workerA.send_message(message=_message(deduplicationId=None, userId='other'))
130
+ await workerA.send_message(message=_message())
131
+ await workerA.send_message(message=_message(), priority=1)
132
+ await workerA.send_message(message=_message())
133
+ assert [(row['content']['userId'], row['priority']) for row in await _rows(queue=workerA)] == [('other', 0), ('user-1', 1)]
134
+ assert (await _claim(queue=workerA)).content['userId'] == 'user-1'
135
+
136
+
119
137
  async def test_processor_fails_message_even_when_notifications_fail(workers: tuple[SqlMessageQueue, SqlMessageQueue]):
120
138
  workerA, _ = workers
121
139
 
@@ -376,6 +394,15 @@ async def test_retry_is_folded_into_an_identical_pending_message(workers: tuple[
376
394
  assert [row['status'] for row in await _rows(queue=workerA)] == ['deduplicated', 'pending']
377
395
 
378
396
 
397
+ async def test_retry_folded_into_a_lower_priority_duplicate_raises_its_priority(workers: tuple[SqlMessageQueue, SqlMessageQueue]):
398
+ workerA, _ = workers
399
+ await workerA.send_message(message=_message(), priority=1)
400
+ message = await _claim(queue=workerA)
401
+ await workerA.send_message(message=_message())
402
+ await workerA.retry_message(message=message)
403
+ assert [(row['status'], row['priority']) for row in await _rows(queue=workerA)] == [('deduplicated', 1), ('pending', 1)]
404
+
405
+
379
406
  async def test_release_hands_the_message_back_without_using_an_attempt(workers: tuple[SqlMessageQueue, SqlMessageQueue]):
380
407
  workerA, workerB = workers
381
408
  await workerA.send_message(message=_message())