diff --git a/.env.example b/.env.example index ec6f96e..7c15793 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,8 @@ S3_ACCESS_KEY= S3_SECRET_ACCESS_KEY= S3_REGION= S3_BUCKET= + +REDIS_PASSWORD= +REDIS_HOST= +REDIS_DB= +REDIS_URL= diff --git a/backend/config.py b/backend/config.py index c462db4..278e6fc 100644 --- a/backend/config.py +++ b/backend/config.py @@ -18,3 +18,5 @@ S3_ACCESS_KEY = os.environ.get("S3_ACCESS_KEY") S3_SECRET_ACCESS_KEY = os.environ.get("S3_SECRET_ACCESS_KEY") S3_REGION = os.environ.get("S3_REGION") S3_BUCKET = os.environ.get("S3_BUCKET") + +RABBITMQ_URL = os.environ.get("RABBITMQ_URL") diff --git a/main.py b/main.py index c8ec926..d8be8aa 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,6 @@ +import contextlib + +import taskiq_fastapi from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware @@ -6,15 +9,29 @@ from fastapi.staticfiles import StaticFiles from starlette.responses import JSONResponse import routers +from task_management import broker from utils.auto_include_routers import auto_include_routers from utils.exceptions import * origins = ["http://localhost:3000"] + +@contextlib.asynccontextmanager +async def lifespan(app): + if not broker.is_worker_process: + await broker.startup() + + yield + + if not broker.is_worker_process: + await broker.shutdown() + + app = FastAPI( separate_input_output_schemas=True, default_response_class=ORJSONResponse, root_path="/api", + # lifespan=lifespan, ) app.add_middleware( @@ -29,6 +46,8 @@ app.add_middleware( minimum_size=1_000, ) +taskiq_fastapi.init(broker, "main:app") + @app.exception_handler(ObjectNotFoundException) async def not_found_exception_handler(request: Request, exc: ObjectNotFoundException): diff --git a/models/__init__.py b/models/__init__.py index 849bc4f..428dff4 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -29,5 +29,7 @@ from .module import ( # noqa: F401 ) from .project import Project as Project from .status import Status as Status, DealStatusHistory as DealStatusHistory +from .task import CeleryTask as CeleryTask, CeleryActiveTask as CeleryActiveTask +from .auth import User as User configure_mappers() diff --git a/models/auth.py b/models/auth.py new file mode 100644 index 0000000..6d598be --- /dev/null +++ b/models/auth.py @@ -0,0 +1,22 @@ +from typing import TYPE_CHECKING + +from sqlalchemy.orm import Mapped, relationship + +from models.base import BaseModel +from models.mixins import IdMixin, SoftDeleteMixin + +if TYPE_CHECKING: + from models import CeleryTask, CeleryActiveTask + + +class User(BaseModel, IdMixin, SoftDeleteMixin): + __tablename__ = "users" + + celery_tasks: Mapped["CeleryTask"] = relationship( + back_populates="user", + lazy="noload", + ) + celery_active_tasks: Mapped["CeleryActiveTask"] = relationship( + back_populates="user", + lazy="noload", + ) diff --git a/models/task.py b/models/task.py new file mode 100644 index 0000000..7feec54 --- /dev/null +++ b/models/task.py @@ -0,0 +1,58 @@ +from datetime import datetime, timezone +from typing import Optional, TYPE_CHECKING + +from sqlalchemy import String, DateTime, ForeignKey +from sqlalchemy.dialects.postgresql import BYTEA +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from models.base import BaseModel +from models.mixins import IdMixin + +if TYPE_CHECKING: + from models.auth import User + + +class CeleryTask(BaseModel, IdMixin): + __tablename__ = "celery_taskmeta" + + task_id: Mapped[int] = mapped_column(String(155), unique=True) + status: Mapped[int] = mapped_column(String(50)) + result: Mapped[bytes] = mapped_column(BYTEA) + date_done: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + default=lambda: datetime.now(timezone.utc), + ) + traceback: Mapped[str] = mapped_column() + name: Mapped[str] = mapped_column(String(155)) + args: Mapped[bytes] = mapped_column(BYTEA) + kwargs: Mapped[bytes] = mapped_column(BYTEA) + worker: Mapped[str] = mapped_column(String(155)) + retries: Mapped[int] = mapped_column() + queue: Mapped[str] = mapped_column(String(155)) + + user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) + user: Mapped["User"] = relationship( + back_populates="celery_tasks", + lazy="noload", + ) + + +class CeleryActiveTask(BaseModel): + __tablename__ = "celery_taskmeta_active" + + task_id: Mapped[str] = mapped_column( + String(155), + primary_key=True, + ) + status: Mapped[str] = mapped_column(String(50)) + name: Mapped[str] = mapped_column(String(155)) + is_notification_hidden: Mapped[bool] = mapped_column( + default=False, + server_default="0", + ) + + user_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id")) + user: Mapped[Optional["User"]] = relationship( + back_populates="celery_active_tasks", + lazy="noload", + ) diff --git a/pyproject.toml b/pyproject.toml index bda2c76..3d53d25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,10 @@ dependencies = [ "starlette>=0.47.2", "python-multipart>=0.0.20", "lexorank-py==0.1.1", + "taskiq>=0.11.19", + "taskiq-aio-pika>=0.4.4", + "taskiq-fastapi>=0.3.5", + "taskiq-postgresql>=0.4.0", ] [dependency-groups] diff --git a/task_management/__init__.py b/task_management/__init__.py new file mode 100644 index 0000000..6c31381 --- /dev/null +++ b/task_management/__init__.py @@ -0,0 +1 @@ +from .taskiq_broker import broker as broker diff --git a/task_management/taskiq_broker.py b/task_management/taskiq_broker.py new file mode 100644 index 0000000..ca6e1f2 --- /dev/null +++ b/task_management/taskiq_broker.py @@ -0,0 +1,15 @@ +from taskiq_aio_pika import AioPikaBroker +from taskiq_postgresql import PostgresqlResultBackend + +from backend.config import RABBITMQ_URL +from backend.session import DATABASE_URL + +result_backend = PostgresqlResultBackend( + dsn=DATABASE_URL, +) + +broker = AioPikaBroker( + RABBITMQ_URL, +).with_result_backend( + result_backend, +) diff --git a/uv.lock b/uv.lock index 8885a67..af51f96 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,19 @@ version = 1 revision = 3 requires-python = ">=3.13" +[[package]] +name = "aio-pika" +version = "9.5.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiormq" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/ad/0ddde89d7a018f4304aac687e5b65c07d308644f51da3c4ae411184bb237/aio_pika-9.5.7.tar.gz", hash = "sha256:0569b59d3c7b36ca76abcb213cdc3677e2a4710a3c371dd27359039f9724f4ee", size = 47298, upload-time = "2025-08-05T18:21:18.397Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/be/9b08e7c4d1b3b9a1184e63965d13c811366444cb42c6e809910ab17e916c/aio_pika-9.5.7-py3-none-any.whl", hash = "sha256:684316a0e92157754bb2d6927c5568fd997518b123add342e97405aa9066772b", size = 54297, upload-time = "2025-08-05T18:21:16.99Z" }, +] + [[package]] name = "aioboto3" version = "15.4.0" @@ -118,6 +131,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/85/13/58b70a580de00893223d61de8fea167877a3aed97d4a5e1405c9159ef925/aioitertools-0.12.0-py3-none-any.whl", hash = "sha256:fc1f5fac3d737354de8831cbba3eb04f79dd649d8f3afb4c5b114925e662a796", size = 24345, upload-time = "2024-09-02T03:34:59.454Z" }, ] +[[package]] +name = "aiormq" +version = "6.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pamqp" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/f6/01bc850db6d9b46ae825e3c373f610b0544e725a1159745a6de99ad0d9f1/aiormq-6.9.2.tar.gz", hash = "sha256:d051d46086079934d3a7157f4d8dcb856b77683c2a94aee9faa165efa6a785d3", size = 30554, upload-time = "2025-10-20T10:49:59.763Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/ec/763b13f148f3760c1562cedb593feaffbae177eeece61af5d0ace7b72a3e/aiormq-6.9.2-py3-none-any.whl", hash = "sha256:ab0f4e88e70f874b0ea344b3c41634d2484b5dc8b17cb6ae0ae7892a172ad003", size = 31829, upload-time = "2025-10-20T10:49:58.547Z" }, +] + [[package]] name = "aiosignal" version = "1.4.0" @@ -362,6 +388,7 @@ dependencies = [ { name = "fastapi-endpoints" }, { name = "fpdf" }, { name = "gunicorn" }, + { name = "lexorank-py" }, { name = "orjson" }, { name = "pathlib" }, { name = "pdfrw" }, @@ -373,6 +400,10 @@ dependencies = [ { name = "reportlab" }, { name = "sqlalchemy", extra = ["asyncio"] }, { name = "starlette" }, + { name = "taskiq" }, + { name = "taskiq-aio-pika" }, + { name = "taskiq-fastapi" }, + { name = "taskiq-postgresql" }, { name = "uvicorn", extra = ["standard"] }, { name = "uvicorn-worker" }, ] @@ -392,6 +423,7 @@ requires-dist = [ { name = "fastapi-endpoints", git = "https://github.com/vladNed/fastapi-endpoints.git?rev=main" }, { name = "fpdf", specifier = ">=1.7.2" }, { name = "gunicorn", specifier = ">=23.0.0" }, + { name = "lexorank-py", specifier = "==0.1.1" }, { name = "orjson", specifier = ">=3.11.1" }, { name = "pathlib", specifier = ">=1.0.1" }, { name = "pdfrw", specifier = ">=0.4" }, @@ -403,6 +435,10 @@ requires-dist = [ { name = "reportlab", specifier = ">=4.4.4" }, { name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0.41" }, { name = "starlette", specifier = ">=0.47.2" }, + { name = "taskiq", specifier = ">=0.11.19" }, + { name = "taskiq-aio-pika", specifier = ">=0.4.4" }, + { name = "taskiq-fastapi", specifier = ">=0.3.5" }, + { name = "taskiq-postgresql", specifier = ">=0.4.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.35.0" }, { name = "uvicorn-worker", specifier = ">=0.3.0" }, ] @@ -671,6 +707,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[package]] +name = "izulu" +version = "0.50.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/58/6d6335c78b7ade54d8a6c6dbaa589e5c21b3fd916341d5a16f774c72652a/izulu-0.50.0.tar.gz", hash = "sha256:cc8e252d5e8560c70b95380295008eeb0786f7b745a405a40d3556ab3252d5f5", size = 48558, upload-time = "2025-03-24T15:52:21.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/9f/bf9d33546bbb6e5e80ebafe46f90b7d8b4a77410b7b05160b0ca8978c15a/izulu-0.50.0-py3-none-any.whl", hash = "sha256:4e9ae2508844e7c5f62c468a8b9e2deba2f60325ef63f01e65b39fd9a6b3fab4", size = 18095, upload-time = "2025-03-24T15:52:19.667Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -692,6 +749,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, ] +[[package]] +name = "lexorank-py" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/b9/a8213f5ed28cbb6f1f24354157745787e1d6776cbbc8106f11c49625d3bd/lexorank-py-0.1.1.tar.gz", hash = "sha256:2045d8cef314cea8f5bd7df036b4dffafab12a569842e60efed6c8f7c3bc31e8", size = 48670, upload-time = "2023-08-26T09:49:41.214Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/ce/563e2935fb89ea66828201d2eba5ce737e76866d1d0067791374007cb380/lexorank_py-0.1.1-py3-none-any.whl", hash = "sha256:b0798b15e7d47d0f94591f872e5d6cf5f2d2ea0186226673141cd1e35f28dfe4", size = 11301, upload-time = "2023-08-26T09:49:40.07Z" }, +] + [[package]] name = "mako" version = "1.3.10" @@ -841,6 +907,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] +[[package]] +name = "pamqp" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/62/35bbd3d3021e008606cd0a9532db7850c65741bbf69ac8a3a0d8cfeb7934/pamqp-3.3.0.tar.gz", hash = "sha256:40b8795bd4efcf2b0f8821c1de83d12ca16d5760f4507836267fd7a02b06763b", size = 30993, upload-time = "2024-01-12T20:37:25.085Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/8d/c1e93296e109a320e508e38118cf7d1fc2a4d1c2ec64de78565b3c445eb5/pamqp-3.3.0-py2.py3-none-any.whl", hash = "sha256:c901a684794157ae39b52cbf700db8c9aae7a470f13528b9d7b4e5f7202f8eb0", size = 33848, upload-time = "2024-01-12T20:37:21.359Z" }, +] + [[package]] name = "pathlib" version = "1.0.1" @@ -993,6 +1068,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, ] +[[package]] +name = "pycron" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5d/340be12ae4a69c33102dfb6ddc1dc6e53e69b2d504fa26b5d34a472c3057/pycron-3.2.0.tar.gz", hash = "sha256:e125a28aca0295769541a40633f70b602579df48c9cb357c36c28d2628ba2b13", size = 4248, upload-time = "2025-06-05T13:24:12.636Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/76/caf316909f4545e7158e0e1defd8956a1da49f4af04f5d16b18c358dfeac/pycron-3.2.0-py3-none-any.whl", hash = "sha256:6d2349746270bd642b71b9f7187cf13f4d9ee2412b4710396a507b5fe4f60dac", size = 4904, upload-time = "2025-06-05T13:24:11.477Z" }, +] + [[package]] name = "pydantic" version = "2.11.7" @@ -1095,6 +1179,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, ] +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -1313,6 +1406,74 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" }, ] +[[package]] +name = "taskiq" +version = "0.11.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "anyio" }, + { name = "importlib-metadata" }, + { name = "izulu" }, + { name = "packaging" }, + { name = "pycron" }, + { name = "pydantic" }, + { name = "pytz" }, + { name = "taskiq-dependencies" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/f8/e26c3a6d169e8ecee228d67d12003d25192d663b27f8007e41575e7a710c/taskiq-0.11.19.tar.gz", hash = "sha256:b6f03e398f0c6e7eb784f22b3c63a2efeb9e113bdce792dbd9ccb81b9edb17c4", size = 55910, upload-time = "2025-10-23T15:01:10.071Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/3a/55fe61591ba4d282cbfc12e66c6d8f5646b9ce81bbfbd958bca55eebbe5b/taskiq-0.11.19-py3-none-any.whl", hash = "sha256:630b39531284f802860fa28ec54707c7e68e57b2011bcb5f8ce9c71508a273f7", size = 81848, upload-time = "2025-10-23T15:01:08.907Z" }, +] + +[[package]] +name = "taskiq-aio-pika" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aio-pika" }, + { name = "taskiq" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/b0/e12a219535ad55034156ab66c3aa07a3673a64d2a6fda83eeabaa6115732/taskiq_aio_pika-0.4.4.tar.gz", hash = "sha256:357f37a6b8828bc61fc4091dcd80b54c6b820eb6774cc72d2f9e372c6a8c0037", size = 7180, upload-time = "2025-10-23T20:16:18.411Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/cb/808172ab28959a6ce9a0638398ed0a59b2ad61d09e87c092b3508f04ad3a/taskiq_aio_pika-0.4.4-py3-none-any.whl", hash = "sha256:8f3405daab41d46f9c5273bdbbb977dd43fd44e82d1b87619078794ae37bcc45", size = 7251, upload-time = "2025-10-23T20:16:17.689Z" }, +] + +[[package]] +name = "taskiq-dependencies" +version = "1.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/90/47a627696e53bfdcacabc3e8c05b73bf1424685bcb5f17209cb8b12da1bf/taskiq_dependencies-1.5.7.tar.gz", hash = "sha256:0d3b240872ef152b719153b9526d866d2be978aeeaea6600e878414babc2dcb4", size = 14875, upload-time = "2025-02-26T22:07:39.876Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/6d/4a012f2de002c2e93273f5e7d3e3feea02f7fdbb7b75ca2ca1dd10703091/taskiq_dependencies-1.5.7-py3-none-any.whl", hash = "sha256:6fcee5d159bdb035ef915d4d848826169b6f06fe57cc2297a39b62ea3e76036f", size = 13801, upload-time = "2025-02-26T22:07:38.622Z" }, +] + +[[package]] +name = "taskiq-fastapi" +version = "0.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastapi" }, + { name = "taskiq" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/29/d4f08a3730d23870f73404d855e5145d13d0bcf1e5376008522804afa51b/taskiq_fastapi-0.3.5.tar.gz", hash = "sha256:0df3df3e7e0a355b9f21a4f81429ad95d0d33af305e75039654ac13ae26c538f", size = 5102, upload-time = "2025-04-20T22:45:21.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/06/6ba382f300aebc5d739cd01d708572329d4360ea0c02b7734583a2894b16/taskiq_fastapi-0.3.5-py3-none-any.whl", hash = "sha256:35553cb05cfb8e34d50179bad747d79a42d782f5b9c529128ad64263af8c246f", size = 5116, upload-time = "2025-04-20T22:45:21.049Z" }, +] + +[[package]] +name = "taskiq-postgresql" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "taskiq" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/1a/dc903813bc238fcba16624e22f959e681f96bd210583a98e77c1d1c7607c/taskiq_postgresql-0.4.0.tar.gz", hash = "sha256:3e8cda663ec2893adfcf2d1f30447b2c03cc46067622e1f9e1c9a45f4e22731f", size = 125271, upload-time = "2025-10-01T17:30:28.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/c7/9a5bec60989cd1d7d97c624fd272a36efe087a0e54ae1f17117c2b56655f/taskiq_postgresql-0.4.0-py3-none-any.whl", hash = "sha256:0e64adc6faf5ffa6b67a59722e56181fc6e79a1bac6d5584fbaca6916bb1ec7b", size = 25844, upload-time = "2025-10-01T17:30:27.348Z" }, +] + [[package]] name = "typer" version = "0.16.0" @@ -1569,3 +1730,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, ] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +]