fastapi-task-manager 0.3.0__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.
@@ -0,0 +1,56 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # IDE ignores
7
+ .idea
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+ .vscode/launch.json
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Environments
55
+ .env
56
+ .venv
@@ -0,0 +1,73 @@
1
+ default_install_hook_types: [ pre-commit, pre-push ]
2
+ default_language_version:
3
+ python: "3.13"
4
+ repos:
5
+ - repo: https://github.com/pre-commit/pre-commit-hooks
6
+ rev: v5.0.0
7
+ hooks:
8
+ - id: check-added-large-files
9
+ stages:
10
+ - pre-commit
11
+ - id: check-merge-conflict
12
+ stages:
13
+ - pre-commit
14
+ - id: end-of-file-fixer
15
+ stages:
16
+ - pre-commit
17
+ - id: mixed-line-ending
18
+ stages:
19
+ - pre-commit
20
+
21
+ - repo: https://github.com/Lucas-C/pre-commit-hooks
22
+ rev: v1.5.5
23
+ hooks:
24
+ - id: remove-tabs
25
+ stages:
26
+ - pre-commit
27
+
28
+ - repo: https://github.com/pre-commit/mirrors-mypy
29
+ rev: v1.15.0
30
+ hooks:
31
+ - id: mypy
32
+ additional_dependencies: [ "types-toml", "types-redis"]
33
+ stages:
34
+ - pre-commit
35
+
36
+ - repo: local
37
+ hooks:
38
+ - id: ruff_format
39
+ name: ruff_format
40
+ entry: ruff format
41
+ language: python
42
+ types: [ python ]
43
+ additional_dependencies: [ "ruff" ]
44
+ stages:
45
+ - pre-commit
46
+ require_serial: true
47
+ - id: ruff_check
48
+ name: ruff_check
49
+ entry: ruff check
50
+ args: [ "--fix", "--unsafe-fixes" ]
51
+ language: python
52
+ types: [ python ]
53
+ additional_dependencies: [ "ruff" ]
54
+ stages:
55
+ - pre-commit
56
+ require_serial: true
57
+ - id: vermin
58
+ name: vermin
59
+ entry: vermin --no-tips -t="3.13-" --violations
60
+ language: python
61
+ types: [ python ]
62
+ additional_dependencies: [ "vermin" ]
63
+ stages:
64
+ - pre-commit
65
+ - id: prune_stale_tags
66
+ name: prune_stale_tags
67
+ entry: python pre_commit_prune_stale_tags.py
68
+ language: python
69
+ pass_filenames: false
70
+ always_run: true
71
+ types: [ python ]
72
+ stages:
73
+ - pre-commit
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Morando Matteo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-task-manager
3
+ Version: 0.3.0
4
+ Summary: A task manager for Fastapi. Robust Scheduling, Distributed Safety
5
+ Project-URL: Homepage, https://github.com/Morry98/fastapi-task-manager
6
+ Project-URL: Bug Tracker, https://github.com/Morry98/fastapi-task-manager/repo/issues
7
+ Author-email: Matteo Morando <matteo.morando98@gmail.com>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2025 Morando Matteo
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: distributed,fastapi,robust,safety,task-manager,task-scheduler
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Framework :: FastAPI
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Operating System :: OS Independent
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Programming Language :: Python :: 3.13
41
+ Classifier: Programming Language :: Python :: 3.14
42
+ Requires-Python: >=3.10
43
+ Requires-Dist: cronexpr>=0.30.0
44
+ Requires-Dist: fastapi>=0.115.13
45
+ Requires-Dist: pydantic>=2.11.7
46
+ Requires-Dist: redis>=6.2.0
47
+ Description-Content-Type: text/markdown
48
+
49
+ # Fastapi Task Manager
50
+ A task manager for Fastapi. Robust Scheduling, Distributed Safety
@@ -0,0 +1,2 @@
1
+ # Fastapi Task Manager
2
+ A task manager for Fastapi. Robust Scheduling, Distributed Safety
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ import subprocess
4
+
5
+
6
+ def get_local_tags():
7
+ """Return a set of all local tag names."""
8
+ out = subprocess.check_output(["git", "tag", "-l"], text=True, stderr=subprocess.DEVNULL) # noqa: S607
9
+ return {line.strip() for line in out.splitlines() if line.strip()}
10
+
11
+
12
+ def get_remote_tags():
13
+ """Return a set of all remote tag names on 'origin'."""
14
+ out = subprocess.check_output(["git", "ls-remote", "--tags", "origin"], text=True, stderr=subprocess.DEVNULL) # noqa: S607
15
+ tags = set()
16
+ for line in out.splitlines():
17
+ # each line is "<hash>\\trefs/tags/<tagname>" or "<hash>\\trefs/tags/<tagname>^{}"
18
+ parts = line.split()
19
+ if len(parts) != 2: # noqa: PLR2004
20
+ continue
21
+ ref = parts[1]
22
+ if not ref.startswith("refs/tags/"):
23
+ continue
24
+ tag = ref[len("refs/tags/") :]
25
+ # strip off ^{} that marks peeled (annotated) tags
26
+ if tag.endswith("^{}"):
27
+ tag = tag[:-3]
28
+ tags.add(tag)
29
+ return tags
30
+
31
+
32
+ def prune_stale_tags():
33
+ local = get_local_tags()
34
+ remote = get_remote_tags()
35
+ to_delete = sorted(local - remote)
36
+ if not to_delete:
37
+ return remote
38
+ # Bulk-delete them:
39
+ subprocess.check_output(["git", "tag", "-d", *to_delete], text=True) # noqa: S603, S607
40
+ return remote
41
+
42
+
43
+ if __name__ == "__main__":
44
+ prune_stale_tags()
@@ -0,0 +1,144 @@
1
+ [project]
2
+ name = "fastapi-task-manager"
3
+ version = "0.3.0"
4
+ description = "A task manager for Fastapi. Robust Scheduling, Distributed Safety"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Matteo Morando", email = "matteo.morando98@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.10"
10
+ keywords = [
11
+ "fastapi",
12
+ "task-manager",
13
+ "task-scheduler",
14
+ "distributed",
15
+ "safety",
16
+ "robust",
17
+ ]
18
+ license = { file = "LICENSE" }
19
+ urls = { "Homepage" = "https://github.com/Morry98/fastapi-task-manager", "Bug Tracker" = "https://github.com/Morry98/fastapi-task-manager/repo/issues" }
20
+ classifiers = [
21
+ "Development Status :: 3 - Alpha",
22
+ "Intended Audience :: Developers",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Programming Language :: Python :: 3.14",
30
+ "Framework :: FastAPI",
31
+ "Operating System :: OS Independent",
32
+ ]
33
+ dependencies = [
34
+ "cronexpr>=0.30.0",
35
+ "fastapi>=0.115.13",
36
+ "pydantic>=2.11.7",
37
+ "redis>=6.2.0",
38
+ ]
39
+
40
+ [project.scripts]
41
+ fastapi-task-manager = "fastapi_task_manager:main"
42
+
43
+ [build-system]
44
+ requires = ["hatchling"]
45
+ build-backend = "hatchling.build"
46
+
47
+ [[tool.uv.index]]
48
+ name = "testpypi"
49
+ url = "https://test.pypi.org/simple/"
50
+ publish-url = "https://test.pypi.org/legacy/"
51
+ explicit = true
52
+
53
+ [tool.ruff]
54
+ show-fixes = true
55
+ line-length = 120
56
+
57
+ [tool.ruff.lint]
58
+ # see https://docs.astral.sh/ruff/rules/ for more info
59
+ select = [
60
+ "F", # Pyflakes
61
+ "E", # pycodestyle errors
62
+ "W", # pycodestyle warnings
63
+ "C90", # mccabe
64
+ "I", # isort
65
+ "N", # pep8-naming
66
+ "S", # flake8-bandit
67
+ "B", # flake8-bugbear
68
+ # "D", # pydocstyle
69
+ "UP", # pyupgrade
70
+ "YTT", # flake8-2020
71
+ # "ANN", # flake8-annotations
72
+ # "BLE", # flake8-blind-except
73
+ # "FBT", # flake8-boolean-trap
74
+ "A", # flake8-builtins
75
+ "COM", # flake8-commas
76
+ "C4", # flake8-comprehensions
77
+ "T10", # flake8-debugger
78
+ "EXE", # flake8-executable
79
+ # "DTZ", # flake8-datetimez
80
+ "DJ", # flake8-django
81
+ "EM", # flake8-errmsg
82
+ "ISC", # flake8-implicit-str-concat
83
+ "ICN", # flake8-import-conventions
84
+ "G", # flake8-logging-format
85
+ # "INP", # flake8-no-pep420
86
+ "PIE", # flake8-pie
87
+ "T20", # flake8-print
88
+ "PYI", # flake8-pyi
89
+ "PT", # flake8-pytest-style
90
+ "Q", # flake8-quotes
91
+ "RSE", # flake8-raise
92
+ "RET", # flake8-return
93
+ "SLF", # flake8-self
94
+ "SIM", # flake8-simplify
95
+ "TID", # flake8-tidy-imports
96
+ "TCH", # flake8-type-checking
97
+ "INT", # flake8-gettext
98
+ "ARG", # flake8-unused-arguments
99
+ # "PTH", # flake8-use-pathlib
100
+ "ERA", # eradicate
101
+ # "PD", # pandas-vet
102
+ # "PGH", # pygrep-hooks
103
+ "PL", # Pylint
104
+ "TRY", # tryceratops
105
+ "NPY", # NumPy-specific rules
106
+ "RUF", # Ruff-specific rules
107
+ ]
108
+ ignore = [
109
+ "B008",
110
+ "N801",
111
+ "N806",
112
+ "PD003",
113
+ "PD011",
114
+ "Q000",
115
+ "S101",
116
+ "S113",
117
+ "S501",
118
+ "RUF012",
119
+ ]
120
+
121
+ [tool.ruff.lint.flake8-quotes]
122
+ inline-quotes = "double"
123
+
124
+ [tool.ruff.lint.mccabe]
125
+ max-complexity = 12
126
+
127
+ [tool.ruff.lint.per-file-ignores]
128
+ "__init__.py" = ["F401"]
129
+ "tmp*.py" = ["T201"]
130
+ "test_*.py" = ["E402", "PLR2004", "SLF001", "ARG001"]
131
+
132
+ [tool.ruff.lint.pylint]
133
+ max-args = 8
134
+
135
+ [tool.mypy]
136
+ no_implicit_optional = true
137
+ check_untyped_defs = true
138
+ warn_redundant_casts = true
139
+ warn_unused_ignores = true
140
+ warn_no_return = true
141
+ warn_unreachable = true
142
+ pretty = true
143
+ ignore_missing_imports = true
144
+ plugins = []
@@ -0,0 +1,2 @@
1
+ from fastapi_task_manager.config import Config
2
+ from fastapi_task_manager.task_manager import TaskManager
@@ -0,0 +1,18 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class Config(BaseModel):
5
+ # --------- Logging config variables ---------
6
+ level: str = "WARNING"
7
+ # --------- End of logging config variables ---------
8
+
9
+ # --------- App config variables ---------
10
+ concurrent_tasks: int = 2
11
+ # --------- End of app config variables ---------
12
+
13
+ # --------- Redis config variables ---------
14
+ redis_host: str | None = None
15
+ redis_port: int = 6379
16
+ redis_password: str | None = None
17
+ redis_db: int = 1 # Default Redis database to use
18
+ # --------- End of redis config variables ---------
@@ -0,0 +1,20 @@
1
+ import asyncio
2
+ from contextlib import asynccontextmanager
3
+
4
+
5
+ class ForceAcquireSemaphore(asyncio.Semaphore):
6
+ @asynccontextmanager
7
+ async def force_acquire(self):
8
+ # bypass the normal acquire() wait and
9
+ # decrement the counter even if it goes < 0
10
+ self._value -= 1
11
+ try:
12
+ yield
13
+ finally:
14
+ if self._value < 0:
15
+ # if the value is negative, we increment it back
16
+ self._value += 1
17
+ else:
18
+ # if the value is not negative, we just release normally
19
+ # this is to ensure that the semaphore can be used normally after force_acquire
20
+ self.release()
@@ -0,0 +1,151 @@
1
+ import asyncio
2
+ import logging
3
+ from collections.abc import Callable
4
+ from datetime import datetime, timezone
5
+ from uuid import uuid4
6
+
7
+ from cronexpr import next_fire
8
+ from redis.asyncio import Redis
9
+
10
+ from fastapi_task_manager.force_acquire_semaphore import ForceAcquireSemaphore
11
+ from fastapi_task_manager.schema.task import Task
12
+
13
+ logger = logging.getLogger("fastapi.task-manager")
14
+
15
+
16
+ class Runner:
17
+ def __init__(
18
+ self,
19
+ redis_client: Redis,
20
+ concurrent_tasks: int,
21
+ ):
22
+ self._uuid: str = str(uuid4().int)
23
+ self._redis_client = redis_client
24
+ self._running_thread: asyncio.Task | None = None
25
+ self._tasks: list[Task] = []
26
+ self._semaphore = ForceAcquireSemaphore(concurrent_tasks)
27
+
28
+ async def start(self) -> None:
29
+ if self._running_thread:
30
+ msg = "Runner is already running."
31
+ logger.warning(msg)
32
+ return
33
+ try:
34
+ pong = await self._redis_client.ping()
35
+ except Exception as e:
36
+ msg = f"Redis ping failed: {e!r}"
37
+ raise ConnectionError(msg) from e
38
+ if not pong:
39
+ msg = "Redis ping returned falsy response"
40
+ raise ConnectionError(msg)
41
+
42
+ self._running_thread = asyncio.create_task(self._run(), name="Runner")
43
+ logger.info("Runner started successfully.")
44
+
45
+ async def stop(self) -> None:
46
+ if not self._running_thread:
47
+ msg = "Runner is not running."
48
+ logger.warning(msg)
49
+ return
50
+ for task in self._tasks:
51
+ if task.running_thread:
52
+ await stop_thread(task.running_thread)
53
+ task.running_thread = None
54
+ await stop_thread(self._running_thread)
55
+ self._running_thread = None
56
+ logger.info("Stopped TaskManager.")
57
+
58
+ def add_task(self, task: Task) -> None:
59
+ for t in self._tasks:
60
+ if t.name == task.name:
61
+ msg = f"Task with name {task.name} already exists."
62
+ raise RuntimeError(msg)
63
+ self._tasks.append(task)
64
+
65
+ async def _run(self):
66
+ while True:
67
+ await asyncio.sleep(0.1)
68
+ try:
69
+ for task in self._tasks:
70
+ if task.running_thread is not None:
71
+ if not task.running_thread.done():
72
+ continue
73
+ # If the task is done, remove it from the running tasks list
74
+ task.running_thread = None
75
+ elif task.next_run <= datetime.now(timezone.utc):
76
+ task.next_run = next_fire(task.expression)
77
+ task.running_thread = asyncio.create_task(self._queue_task(task), name=task.name)
78
+ except asyncio.CancelledError:
79
+ logger.info("Runner task was cancelled.")
80
+ return
81
+ except Exception:
82
+ logger.exception("Error in Runner task loop.")
83
+ continue
84
+
85
+ async def _queue_task(self, task: Task):
86
+ if task.high_priority:
87
+ async with self._semaphore.force_acquire():
88
+ await self._run_task(task)
89
+ else:
90
+ async with self._semaphore:
91
+ await self._run_task(task)
92
+
93
+ async def _run_task(self, task: Task) -> None:
94
+ try:
95
+ redis_key_exists = await self._redis_client.exists(task.name + "_valid")
96
+ if redis_key_exists:
97
+ return
98
+
99
+ redis_uuid_exists = await self._redis_client.exists(task.name + "_runner_uuid")
100
+ if not redis_uuid_exists:
101
+ await self._redis_client.set(task.name + "_runner_uuid", self._uuid, ex=5)
102
+ await asyncio.sleep(0.2)
103
+ redis_uuid_b = await self._redis_client.get(task.name + "_runner_uuid")
104
+ if redis_uuid_b is None:
105
+ return
106
+ if redis_uuid_b.decode("utf-8") != self._uuid:
107
+ return
108
+
109
+ thread = asyncio.create_task(run_function(task.function))
110
+ while not thread.done():
111
+ await self._redis_client.set(task.name + "_runner_uuid", self._uuid, ex=1)
112
+ await asyncio.sleep(0.1)
113
+
114
+ task.next_run = next_fire(task.expression)
115
+ ex = int((task.next_run - datetime.now(timezone.utc)).total_seconds())
116
+ if ex <= 0:
117
+ return
118
+ await self._redis_client.set(
119
+ task.name + "_valid",
120
+ 1,
121
+ ex=ex,
122
+ )
123
+ await self._redis_client.delete(task.name + "_runner_uuid")
124
+
125
+ except asyncio.CancelledError:
126
+ msg = f"Task {task.name} was cancelled."
127
+ logger.info(msg)
128
+ except Exception:
129
+ logger.exception("Failed to run task.")
130
+
131
+
132
+ async def stop_thread(running_task: asyncio.Task) -> None:
133
+ if not running_task.done():
134
+ running_task.cancel()
135
+ try:
136
+ await running_task
137
+ except asyncio.CancelledError:
138
+ return
139
+ except Exception:
140
+ msg = "Error stopping Runner"
141
+ logger.exception(msg)
142
+
143
+
144
+ async def run_function(function: Callable):
145
+ try:
146
+ if asyncio.iscoroutinefunction(function):
147
+ await function()
148
+ else:
149
+ await asyncio.to_thread(function)
150
+ except Exception:
151
+ logger.exception("Error running function.")
@@ -0,0 +1,22 @@
1
+ import asyncio
2
+ from collections.abc import Callable
3
+ from datetime import datetime, timezone
4
+
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class Task(BaseModel):
9
+ """Schema for a task in the task manager."""
10
+
11
+ model_config = {
12
+ "arbitrary_types_allowed": True,
13
+ }
14
+
15
+ function: Callable
16
+ expression: str
17
+ name: str
18
+ description: str | None = None
19
+ tags: list[str] | None = None
20
+ high_priority: bool = False
21
+ next_run: datetime = datetime.min.replace(tzinfo=timezone.utc)
22
+ running_thread: asyncio.Task | None = None
@@ -0,0 +1,97 @@
1
+ import logging
2
+ from collections.abc import Callable
3
+ from contextlib import asynccontextmanager
4
+
5
+ from fastapi import FastAPI
6
+ from redis.asyncio import Redis
7
+
8
+ from fastapi_task_manager.config import Config
9
+ from fastapi_task_manager.runner import Runner
10
+ from fastapi_task_manager.schema.task import Task
11
+
12
+ logger = logging.getLogger("fastapi.task-manager")
13
+
14
+
15
+ class TaskManager:
16
+ def __init__(
17
+ self,
18
+ app: FastAPI,
19
+ redis_client: Redis,
20
+ config: Config | None = None,
21
+ ):
22
+ self._config = config or Config()
23
+ self._app = app
24
+ self._running = False
25
+ self._runner = Runner(
26
+ redis_client=redis_client,
27
+ concurrent_tasks=self._config.concurrent_tasks,
28
+ )
29
+
30
+ logger.setLevel(self._config.level.upper().strip())
31
+
32
+ self.append_to_app_lifecycle(app)
33
+
34
+ def append_to_app_lifecycle(self, app: FastAPI) -> None:
35
+ """Automatically start/stop with app lifecycle."""
36
+
37
+ # Check if app already has a lifespan
38
+ existing_lifespan = getattr(app.router, "lifespan_context", None)
39
+
40
+ @asynccontextmanager
41
+ async def lifespan(app):
42
+ await self.start()
43
+ try:
44
+ if existing_lifespan:
45
+ # If there's an existing lifespan, run it
46
+ async with existing_lifespan(app):
47
+ yield
48
+ else:
49
+ yield
50
+ finally:
51
+ await self.stop()
52
+
53
+ # Set the new lifespan
54
+ app.router.lifespan_context = lifespan
55
+
56
+ async def start(self) -> None:
57
+ if self._running:
58
+ logger.warning("TaskManager is already running.")
59
+ return
60
+ self._running = True
61
+ logger.info("Starting TaskManager...")
62
+ await self._runner.start()
63
+ logger.info("Started TaskManager.")
64
+
65
+ async def stop(self) -> None:
66
+ if not self._running:
67
+ logger.warning("TaskManager is not running.")
68
+ return
69
+ self._running = False
70
+ logger.info("Stopping TaskManager...")
71
+ await self._runner.stop()
72
+ logger.info("Stopped TaskManager.")
73
+
74
+ def manager(
75
+ self,
76
+ expr: str,
77
+ tags: list[str] | None = None,
78
+ name: str | None = None,
79
+ description: str | None = None,
80
+ high_priority: bool = False,
81
+ ):
82
+ """Decorator for creating task."""
83
+
84
+ def wrapper(func: Callable):
85
+ task = Task(
86
+ function=func,
87
+ expression=expr,
88
+ name=name or func.__name__,
89
+ description=description,
90
+ tags=tags,
91
+ high_priority=high_priority,
92
+ )
93
+ self._runner.add_task(task)
94
+
95
+ return func
96
+
97
+ return wrapper
@@ -0,0 +1,291 @@
1
+ version = 1
2
+ revision = 1
3
+ requires-python = ">=3.10"
4
+
5
+ [[package]]
6
+ name = "annotated-types"
7
+ version = "0.7.0"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "anyio"
16
+ version = "4.9.0"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ dependencies = [
19
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
20
+ { name = "idna" },
21
+ { name = "sniffio" },
22
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
23
+ ]
24
+ sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 }
25
+ wheels = [
26
+ { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 },
27
+ ]
28
+
29
+ [[package]]
30
+ name = "async-timeout"
31
+ version = "5.0.1"
32
+ source = { registry = "https://pypi.org/simple" }
33
+ sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274 }
34
+ wheels = [
35
+ { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233 },
36
+ ]
37
+
38
+ [[package]]
39
+ name = "cronexpr"
40
+ version = "0.30.0"
41
+ source = { registry = "https://pypi.org/simple" }
42
+ sdist = { url = "https://files.pythonhosted.org/packages/01/2a/72d9b939fa00a9e77c613e7d7cee361a81d9df2d036493a522517d083a46/cronexpr-0.30.0.tar.gz", hash = "sha256:994660fdf9195bc2456e69fa447fb06d8256cdfd7fe8829be82add10b9f3e9ac", size = 9350 }
43
+ wheels = [
44
+ { url = "https://files.pythonhosted.org/packages/78/22/da06c70e633d7939c7f0b596835a70c0c2061e8f1bce1937626560368bee/cronexpr-0.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae2c90b6240a3be1a4e6ee118055b1212338369ad4524c9e56b7f1ba1495884d", size = 58617 },
45
+ { url = "https://files.pythonhosted.org/packages/ce/1a/007ef33feeaf8e84da091b1440ceb938e7d551eea8a5684863ec5da3427a/cronexpr-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3faa4a3e0416f7ada2285af2134b52e4c1958f5bb4838202ff639c7392fcf443", size = 55633 },
46
+ { url = "https://files.pythonhosted.org/packages/94/c4/ee667eaf1605ce132f23cb4649ee5320467c3a627c1a73dc3645d60a409a/cronexpr-0.30.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8336da63137deca455f5170162fd27a016043e6f5ad1a83d884ad17510d5e80", size = 93074 },
47
+ { url = "https://files.pythonhosted.org/packages/a2/3a/995d76d3b00588f2c509e704606fec063c59341125f5230912485f42e79b/cronexpr-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ba3c473a7fcced90334931702aca389c058f7aa60f999847a2bff76906f1649", size = 86902 },
48
+ { url = "https://files.pythonhosted.org/packages/5a/ef/1eab22c6cf02b2e10b63a1648030cbf31e8ff31a1e485a76d05e9fb1f7c4/cronexpr-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c40467180ea05dd7dc5ce191a02a4873e01e0f3297dfe4215eac99c93acd5060", size = 1159329 },
49
+ { url = "https://files.pythonhosted.org/packages/b5/66/31eef1d473bd3fa4401eacb3a59f9448cddb74525fee4becb0c326254f4d/cronexpr-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:281bce47793fddba003f246858aae0b8263d028128e96c1e6fa99484870ae86f", size = 1061752 },
50
+ { url = "https://files.pythonhosted.org/packages/eb/85/d046fc7f717123aec290c709cc7bcadb6ea10259abc8fbbfc8a492920e58/cronexpr-0.30.0-cp310-cp310-win32.whl", hash = "sha256:3cfcc338cfcc9b69e09bdb2ba11a0aaa1603c52e431cfd26e92e227831daa22b", size = 58659 },
51
+ { url = "https://files.pythonhosted.org/packages/e7/7b/0499b2c5484305606d9244d90adb354021a92bdb8748e62a956ec6694b23/cronexpr-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:ad6e6e0616fa731dbdb755e6fa54d28a0bf7915e9fe8ea30b8f2c497d35e00f0", size = 63256 },
52
+ { url = "https://files.pythonhosted.org/packages/2f/d5/ce627a6d1e4a268e6d88811f01e4edbc5d4bedfb8f3ff41fa10182692f7a/cronexpr-0.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:617ad204f1db8c09ab57e5e15698b29d8e83634569e953c22b4ca2fb1a631a63", size = 59958 },
53
+ { url = "https://files.pythonhosted.org/packages/49/fa/751bf123fb0c5e10d37220b690991aa617cebd96e2bbc23851a79b043b46/cronexpr-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f4b10220ed3e63a4bbaffa835faa1104c1e8a13c905e9413a958ed3c566da268", size = 56983 },
54
+ { url = "https://files.pythonhosted.org/packages/3c/21/8291ad371c9e4bef2b7f88817e66c287344b5dd35cc5fb69317f4940b6fe/cronexpr-0.30.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6504804326fd99362e20a127e61c031b512c7437a929377c624fb5f4675351c", size = 94254 },
55
+ { url = "https://files.pythonhosted.org/packages/45/06/08eb634f5e9ee07063db2ccd793dfda46f7bb245a0ef5ed66420f733e402/cronexpr-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45bde59a340c6c0019ac83cbc0719f618dd7ec6f293f7c888a43c579ee47c644", size = 88478 },
56
+ { url = "https://files.pythonhosted.org/packages/16/08/56338ba97756e6f380c9cbb1671f504584e660c3315f55879b1cd1be1b10/cronexpr-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:02b69b6fbd4b0b1e862aa5b7cc7f7fd07462804d04036bc493b03a28cda0fa06", size = 1160044 },
57
+ { url = "https://files.pythonhosted.org/packages/e8/11/09f1f0c13842ad4866c2c0dc698a570ffd3118961193bbcf9674600cd646/cronexpr-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2f88a6320956d5b98793d1b82753556c766a113db25cef53429ac30cb09f9e4a", size = 1062723 },
58
+ { url = "https://files.pythonhosted.org/packages/6d/74/40a91b2c3e735a5d1f8aa5826700dbcaecdb1e654dffde777007f7110e16/cronexpr-0.30.0-cp311-cp311-win32.whl", hash = "sha256:f3f05a53541af3b646912dced6fa0b160435a847a9666bf88783de02474d903f", size = 59856 },
59
+ { url = "https://files.pythonhosted.org/packages/aa/84/e9eabcc97939546535739d5f40013bed80014d3ad5df13d3983c82c3c15d/cronexpr-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:1cf831566fbda55fc5083d101c957921fda0f0cfef4e4e415b21d86bc6a57964", size = 64358 },
60
+ { url = "https://files.pythonhosted.org/packages/3d/6b/7c4d19220b96cd61d9e2b0f0874e8f5c79a26ea473f4728a2fcc2ec908ef/cronexpr-0.30.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b59f8991ff1e2cdfceca780104cf49411ee5706b03abc22aa7ce2133a36109dc", size = 59114 },
61
+ { url = "https://files.pythonhosted.org/packages/88/1b/0bab92c881b06b8c910d277cea132a3db61f0662eb8977da0428884f2bb4/cronexpr-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a6cfbd0d440f9072c9af0fc1891f671ef7930c2bd033af409844fb37a8e58f9b", size = 56261 },
62
+ { url = "https://files.pythonhosted.org/packages/2a/65/1e12be06033577ff8b5c3f1680de59ebf39f5147f1672a5675a5b677aa74/cronexpr-0.30.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ba6cfa84429af7374f4d0fa824d16928b725de7996ec08ec69873dc9584e4df", size = 93894 },
63
+ { url = "https://files.pythonhosted.org/packages/38/6a/d074ca8bf64c42ae9693f4f34554794b9ecbea4d6558143c87e24b548a2a/cronexpr-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3567d992504372edc3ab97b40985e40b29e0b54354c46cdb6fdb46cdb4f75f", size = 88055 },
64
+ { url = "https://files.pythonhosted.org/packages/74/9d/1f72e5bf4a9fcc868ffab9ac1ca52b6b1014a657545f0dd9a713dd2453cf/cronexpr-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8ac28315c1650173d6bc2aac1fd802963f8120196fd070a9ad747e458d3ea7d2", size = 1159197 },
65
+ { url = "https://files.pythonhosted.org/packages/bb/5b/6c432619c401bc4e582441cb88819e1607c40aaaba2c6b07f66337b01899/cronexpr-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee616c723fbb129c7e7aaa0d6158c043d4dfc90df4dc7d35f1f257b7ab1c1954", size = 1062002 },
66
+ { url = "https://files.pythonhosted.org/packages/88/5e/15344b0763b5510658293b819f56472b2b35032149c4d09d5078312226d0/cronexpr-0.30.0-cp312-cp312-win32.whl", hash = "sha256:7b1fbdd2eee6ed709320a2a20f63283a53b3b391fdc61e5275c7299daadf7ae3", size = 60061 },
67
+ { url = "https://files.pythonhosted.org/packages/f4/59/210fd2810429df7964f692b349deb82fff50db2742d9d639b87e4dc9cfeb/cronexpr-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:45fe11c95c83d800e9558a03a77a91b78af34898ce30dae895c06a31975b2a3c", size = 64438 },
68
+ { url = "https://files.pythonhosted.org/packages/3d/1b/a0a5ac3cc99268cda3a438ca9b55d12567180cc26d3191291b4343a8ee06/cronexpr-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc186d1a3b99cbaed6363c1ee79b5eeff724835cc4c12f39188b6db981681d1f", size = 59131 },
69
+ { url = "https://files.pythonhosted.org/packages/d8/1f/27b43b778594d86c6b419ff4cd2b015c49c0ff204796b47e9626acdf1f87/cronexpr-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:da9db9656be179baa3b30f4484998faa8d7a9f491b467b4a620daba5ea29a2d0", size = 56335 },
70
+ { url = "https://files.pythonhosted.org/packages/6c/7b/4249bb448829498c3e7783e66d188993c085b4294c8d5f9808071e163e60/cronexpr-0.30.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7038afad7d188ddc98caca3891640d556425f6c10de97de0da64dd7a9126974", size = 93819 },
71
+ { url = "https://files.pythonhosted.org/packages/4b/71/1516e363a4b1386c0c35d716e0b798272415c1da215c9b2c6bdf0a99516e/cronexpr-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac76e65edc8491048c4a86d1785f12952a80f1df1bdf6662114573856a2ea921", size = 88083 },
72
+ { url = "https://files.pythonhosted.org/packages/05/81/8c00fbeda05eff7fb226563f5ae6beb57da122b4729d5bb8961e9c0eaae3/cronexpr-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:99aefb61623915657fe29db28d0b407e2f748776d25acf31d4d45b2289947f8f", size = 1159363 },
73
+ { url = "https://files.pythonhosted.org/packages/ae/a6/44d54958f03a147a64169f534733f38e056bdad967e4ca38188b49f1ec97/cronexpr-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11ee708ff4da288a1e22757f4425e031809603efac6437fd33a4f91bff0993e9", size = 1062004 },
74
+ { url = "https://files.pythonhosted.org/packages/bd/fa/c6cf80efef0e15e969c6aace48fcbee8c9bdfe51c14cb9a24f3a1f7e0a8d/cronexpr-0.30.0-cp313-cp313-win32.whl", hash = "sha256:591719b48ccf6c95b3aa784f3de9e0f9e8d720ed76d4ae52a39d94f173dd0413", size = 60132 },
75
+ { url = "https://files.pythonhosted.org/packages/7b/74/721872a84b05e0a424ac24c2dce09571605d72c9b32947545bdaecbc43e5/cronexpr-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:866aad7bd871bf9dec694f2b747861d3dc3681debc3bf2ef0bc78346418da574", size = 64461 },
76
+ { url = "https://files.pythonhosted.org/packages/7c/f3/e727a8389514088b8e4474f122da0886d97c113a68480a8244f7c730d86b/cronexpr-0.30.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5f1368730689b749e8cebb6bfa3ac0fafa65529fb7df7d05c8b59924b2d647", size = 59428 },
77
+ { url = "https://files.pythonhosted.org/packages/bd/45/1987d2a75ce51aff6b0565f052f429ac47d517a776f5322d57d8120d82f3/cronexpr-0.30.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ef7ac0861c1149cb39f2768b49921e3736f38fc4f81d03a885041b64b35f6d2d", size = 56020 },
78
+ { url = "https://files.pythonhosted.org/packages/23/77/d06d4584f3d8d757044418eedd477030b5d4429f060d74cd837a58ad3f0c/cronexpr-0.30.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acc48680bd667abe56b3d3cccfad2153e2bb38bb02c801ae9743cac9837d37d8", size = 92924 },
79
+ { url = "https://files.pythonhosted.org/packages/99/aa/77046ee80228448541165e0a2ee9b491f2b5722274aa146786f32278229b/cronexpr-0.30.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f381e2f40e54df5676401e4226b3908273ea6cb195cc687204b1029933836717", size = 86940 },
80
+ { url = "https://files.pythonhosted.org/packages/e1/a0/5d266c0d370aec62a49003fed1648518e2c24908f9719a7563e2a689c05c/cronexpr-0.30.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1e1117188c05595e2bb404601cb122d62773eb087d52dd2f905bea7f5690145f", size = 63497 },
81
+ ]
82
+
83
+ [[package]]
84
+ name = "exceptiongroup"
85
+ version = "1.3.0"
86
+ source = { registry = "https://pypi.org/simple" }
87
+ dependencies = [
88
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
89
+ ]
90
+ sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 }
91
+ wheels = [
92
+ { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 },
93
+ ]
94
+
95
+ [[package]]
96
+ name = "fastapi"
97
+ version = "0.115.13"
98
+ source = { registry = "https://pypi.org/simple" }
99
+ dependencies = [
100
+ { name = "pydantic" },
101
+ { name = "starlette" },
102
+ { name = "typing-extensions" },
103
+ ]
104
+ sdist = { url = "https://files.pythonhosted.org/packages/20/64/ec0788201b5554e2a87c49af26b77a4d132f807a0fa9675257ac92c6aa0e/fastapi-0.115.13.tar.gz", hash = "sha256:55d1d25c2e1e0a0a50aceb1c8705cd932def273c102bff0b1c1da88b3c6eb307", size = 295680 }
105
+ wheels = [
106
+ { url = "https://files.pythonhosted.org/packages/59/4a/e17764385382062b0edbb35a26b7cf76d71e27e456546277a42ba6545c6e/fastapi-0.115.13-py3-none-any.whl", hash = "sha256:0a0cab59afa7bab22f5eb347f8c9864b681558c278395e94035a741fc10cd865", size = 95315 },
107
+ ]
108
+
109
+ [[package]]
110
+ name = "fastapi-task-manager"
111
+ version = "0.1.4"
112
+ source = { editable = "." }
113
+ dependencies = [
114
+ { name = "cronexpr" },
115
+ { name = "fastapi" },
116
+ { name = "pydantic" },
117
+ { name = "redis" },
118
+ ]
119
+
120
+ [package.metadata]
121
+ requires-dist = [
122
+ { name = "cronexpr", specifier = ">=0.30.0" },
123
+ { name = "fastapi", specifier = ">=0.115.13" },
124
+ { name = "pydantic", specifier = ">=2.11.7" },
125
+ { name = "redis", specifier = ">=6.2.0" },
126
+ ]
127
+
128
+ [[package]]
129
+ name = "idna"
130
+ version = "3.10"
131
+ source = { registry = "https://pypi.org/simple" }
132
+ sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
133
+ wheels = [
134
+ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
135
+ ]
136
+
137
+ [[package]]
138
+ name = "pydantic"
139
+ version = "2.11.7"
140
+ source = { registry = "https://pypi.org/simple" }
141
+ dependencies = [
142
+ { name = "annotated-types" },
143
+ { name = "pydantic-core" },
144
+ { name = "typing-extensions" },
145
+ { name = "typing-inspection" },
146
+ ]
147
+ sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350 }
148
+ wheels = [
149
+ { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782 },
150
+ ]
151
+
152
+ [[package]]
153
+ name = "pydantic-core"
154
+ version = "2.33.2"
155
+ source = { registry = "https://pypi.org/simple" }
156
+ dependencies = [
157
+ { name = "typing-extensions" },
158
+ ]
159
+ sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 }
160
+ wheels = [
161
+ { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817 },
162
+ { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357 },
163
+ { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011 },
164
+ { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730 },
165
+ { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178 },
166
+ { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462 },
167
+ { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652 },
168
+ { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306 },
169
+ { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720 },
170
+ { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915 },
171
+ { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884 },
172
+ { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496 },
173
+ { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019 },
174
+ { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584 },
175
+ { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071 },
176
+ { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823 },
177
+ { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792 },
178
+ { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338 },
179
+ { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998 },
180
+ { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200 },
181
+ { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890 },
182
+ { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359 },
183
+ { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883 },
184
+ { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074 },
185
+ { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538 },
186
+ { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909 },
187
+ { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786 },
188
+ { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000 },
189
+ { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996 },
190
+ { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957 },
191
+ { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199 },
192
+ { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296 },
193
+ { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109 },
194
+ { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028 },
195
+ { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044 },
196
+ { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881 },
197
+ { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034 },
198
+ { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187 },
199
+ { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 },
200
+ { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 },
201
+ { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 },
202
+ { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 },
203
+ { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 },
204
+ { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 },
205
+ { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 },
206
+ { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 },
207
+ { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 },
208
+ { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 },
209
+ { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 },
210
+ { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 },
211
+ { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 },
212
+ { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 },
213
+ { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 },
214
+ { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 },
215
+ { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 },
216
+ { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 },
217
+ { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 },
218
+ { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 },
219
+ { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982 },
220
+ { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412 },
221
+ { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749 },
222
+ { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527 },
223
+ { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225 },
224
+ { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490 },
225
+ { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525 },
226
+ { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446 },
227
+ { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678 },
228
+ { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200 },
229
+ { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123 },
230
+ { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852 },
231
+ { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484 },
232
+ { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896 },
233
+ { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475 },
234
+ { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013 },
235
+ { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715 },
236
+ { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757 },
237
+ ]
238
+
239
+ [[package]]
240
+ name = "redis"
241
+ version = "6.2.0"
242
+ source = { registry = "https://pypi.org/simple" }
243
+ dependencies = [
244
+ { name = "async-timeout", marker = "python_full_version < '3.11.3'" },
245
+ ]
246
+ sdist = { url = "https://files.pythonhosted.org/packages/ea/9a/0551e01ba52b944f97480721656578c8a7c46b51b99d66814f85fe3a4f3e/redis-6.2.0.tar.gz", hash = "sha256:e821f129b75dde6cb99dd35e5c76e8c49512a5a0d8dfdc560b2fbd44b85ca977", size = 4639129 }
247
+ wheels = [
248
+ { url = "https://files.pythonhosted.org/packages/13/67/e60968d3b0e077495a8fee89cf3f2373db98e528288a48f1ee44967f6e8c/redis-6.2.0-py3-none-any.whl", hash = "sha256:c8ddf316ee0aab65f04a11229e94a64b2618451dab7a67cb2f77eb799d872d5e", size = 278659 },
249
+ ]
250
+
251
+ [[package]]
252
+ name = "sniffio"
253
+ version = "1.3.1"
254
+ source = { registry = "https://pypi.org/simple" }
255
+ sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
256
+ wheels = [
257
+ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
258
+ ]
259
+
260
+ [[package]]
261
+ name = "starlette"
262
+ version = "0.46.2"
263
+ source = { registry = "https://pypi.org/simple" }
264
+ dependencies = [
265
+ { name = "anyio" },
266
+ ]
267
+ sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846 }
268
+ wheels = [
269
+ { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 },
270
+ ]
271
+
272
+ [[package]]
273
+ name = "typing-extensions"
274
+ version = "4.14.0"
275
+ source = { registry = "https://pypi.org/simple" }
276
+ sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423 }
277
+ wheels = [
278
+ { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839 },
279
+ ]
280
+
281
+ [[package]]
282
+ name = "typing-inspection"
283
+ version = "0.4.1"
284
+ source = { registry = "https://pypi.org/simple" }
285
+ dependencies = [
286
+ { name = "typing-extensions" },
287
+ ]
288
+ sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 }
289
+ wheels = [
290
+ { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 },
291
+ ]