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.
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/CHANGELOG.md +1 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/Dockerfile +1 -1
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/PKG-INFO +10 -10
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/sql.py +21 -6
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/kiba_core.egg-info/PKG-INFO +10 -10
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/kiba_core.egg-info/requires.txt +9 -9
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/pyproject.toml +11 -11
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/queues/test_sql_queue.py +27 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/uv.lock +354 -354
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/.github/pull_request_template.md +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/.github/workflows/deploy.yml +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/.github/workflows/publish.yml +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/.github/workflows/pull-request.yml +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/.github/workflows/release.yml +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/.gitignore +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/README.md +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/api_request.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/api_response.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/authorizer.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/default_routes.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/health.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/json_route.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/middleware/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/middleware/database_connection_middleware.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/middleware/exception_handling_middleware.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/middleware/logging_middleware.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/middleware/origin_ip_middleware.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/middleware/server_headers_middleware.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/openapi.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/rate_limit.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/request_body.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/request_context.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/route.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/route_auth.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/route_metadata.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/streaming_json_route.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/api/uploaded_file.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/aws_requester.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/caching/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/caching/cache.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/caching/dict_cache.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/caching/file_cache.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/cron/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/cron/api_request_cron_job_processor.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/cron/cron_executor.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/cron/cron_job_processor.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/cron/model.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/exceptions.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/http/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/http/basic_authentication.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/http/jwt.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/http/rest_method.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/locks/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/locks/lock.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/locks/model.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/locks/sql.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/logging.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/notifications/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/notifications/discord_client.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/notifications/notification_client.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/notifications/slack_client.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/py.typed +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/aqs.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/cosmos.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/message_queue.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/message_queue_processor.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/model.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/queues/sqs.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/requester/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/requester/requester.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/s3_manager.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/service_client.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/store/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/store/database.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/store/entity_repository.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/store/retriever.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/store/saver.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/async_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/chain_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/date_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/dict_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/file_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/hashing_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/http_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/json_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/list_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/process_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/string_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/typing_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/url_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/util/value_holder.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/web3/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/web3/eth_client.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/web3/eth_client_registry.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/core/web3/multicall3.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/kiba_core.egg-info/SOURCES.txt +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/kiba_core.egg-info/dependency_links.txt +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/kiba_core.egg-info/top_level.txt +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/makefile +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/setup.cfg +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_api_response.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_authorizer.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_database_connection_middleware.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_exception_handling_middleware.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_json_route.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_openapi.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_origin_ip_middleware.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_rate_limit.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_request_context.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_route.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/api/test_streaming_json_route.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/caching/test_dict_cache.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/caching/test_file_cache.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/cron/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/cron/test_cron_executor.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/locks/test_sql_lock.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/queues/conftest.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/queues/test_aqs_queue.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/queues/test_cosmos_queue.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/queues/test_message_queue_processor.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/queues/test_sqs_queue.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/requester/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/requester/test_requester.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/test_database.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/test_entity_repository.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/test_exceptions.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/test_logging.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_async_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_chain_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_date_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_dict_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_file_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_json_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_list_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_process_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_string_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/util/test_url_util.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/web3/__init__.py +0 -0
- {kiba_core-0.5.3.dev91 → kiba_core-0.5.3.dev93}/tests/web3/test_eth_client_registry.py +0 -0
- {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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kiba-core
|
|
3
|
-
Version: 0.5.3.
|
|
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.
|
|
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.
|
|
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.
|
|
19
|
+
Requires-Dist: aiobotocore>=3.10.0; extra == "storage"
|
|
20
20
|
Provides-Extra: queues
|
|
21
|
-
Requires-Dist: aiobotocore>=3.
|
|
21
|
+
Requires-Dist: aiobotocore>=3.10.0; extra == "queues"
|
|
22
22
|
Provides-Extra: queue-sqs
|
|
23
|
-
Requires-Dist: aiobotocore>=3.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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],
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
19
|
+
Requires-Dist: aiobotocore>=3.10.0; extra == "storage"
|
|
20
20
|
Provides-Extra: queues
|
|
21
|
-
Requires-Dist: aiobotocore>=3.
|
|
21
|
+
Requires-Dist: aiobotocore>=3.10.0; extra == "queues"
|
|
22
22
|
Provides-Extra: queue-sqs
|
|
23
|
-
Requires-Dist: aiobotocore>=3.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
6
|
+
fastapi>=0.162.0
|
|
7
7
|
uvicorn[standard]>=0.54.0
|
|
8
8
|
|
|
9
9
|
[core-api]
|
|
10
|
-
starlette>=1.
|
|
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.
|
|
18
|
+
sqlalchemy[asyncio]>=2.1.6
|
|
19
19
|
asyncpg>=0.32.0
|
|
20
20
|
|
|
21
21
|
[database-sqlite]
|
|
22
|
-
sqlalchemy[asyncio]>=2.1.
|
|
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.
|
|
32
|
+
aiobotocore>=3.10.0
|
|
33
33
|
|
|
34
34
|
[queues]
|
|
35
|
-
aiobotocore>=3.
|
|
35
|
+
aiobotocore>=3.10.0
|
|
36
36
|
|
|
37
37
|
[requester]
|
|
38
|
-
httpx2>=2.
|
|
38
|
+
httpx2>=2.16.0
|
|
39
39
|
|
|
40
40
|
[storage]
|
|
41
|
-
aiobotocore>=3.
|
|
41
|
+
aiobotocore>=3.10.0
|
|
42
42
|
|
|
43
43
|
[types]
|
|
44
|
-
types-aiobotocore[essential]>=3.
|
|
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.
|
|
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.
|
|
15
|
+
"fastapi>=0.162.0",
|
|
16
16
|
"uvicorn[standard]>=0.54.0",
|
|
17
17
|
]
|
|
18
18
|
core-api = [
|
|
19
|
-
"starlette>=1.
|
|
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.
|
|
24
|
+
"aiobotocore>=3.10.0",
|
|
25
25
|
]
|
|
26
26
|
# TODO(krishan711): remove this in next major update
|
|
27
27
|
queues = [
|
|
28
|
-
"aiobotocore>=3.
|
|
28
|
+
"aiobotocore>=3.10.0",
|
|
29
29
|
]
|
|
30
30
|
queue-sqs = [
|
|
31
|
-
"aiobotocore>=3.
|
|
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.
|
|
40
|
+
"sqlalchemy[asyncio]>=2.1.6",
|
|
41
41
|
"asyncpg>=0.32.0",
|
|
42
42
|
]
|
|
43
43
|
database-sqlite = [
|
|
44
|
-
"sqlalchemy[asyncio]>=2.1.
|
|
44
|
+
"sqlalchemy[asyncio]>=2.1.6",
|
|
45
45
|
"aiosqlite>=0.22.1",
|
|
46
46
|
]
|
|
47
47
|
requester = [
|
|
48
|
-
"httpx2>=2.
|
|
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.
|
|
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.
|
|
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())
|