engin 0.0.7__tar.gz → 0.0.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. {engin-0.0.7 → engin-0.0.8}/CHANGELOG.md +8 -0
  2. {engin-0.0.7 → engin-0.0.8}/PKG-INFO +7 -1
  3. {engin-0.0.7 → engin-0.0.8}/examples/fastapi/main.py +3 -1
  4. {engin-0.0.7 → engin-0.0.8}/examples/simple/main.py +2 -1
  5. {engin-0.0.7 → engin-0.0.8}/pyproject.toml +15 -1
  6. {engin-0.0.7 → engin-0.0.8}/src/engin/_dependency.py +4 -1
  7. {engin-0.0.7 → engin-0.0.8}/src/engin/_engin.py +5 -3
  8. engin-0.0.8/src/engin/_graph.py +39 -0
  9. engin-0.0.8/src/engin/scripts/graph.py +122 -0
  10. engin-0.0.8/tests/conftest.py +0 -0
  11. {engin-0.0.7 → engin-0.0.8}/tests/test_engin.py +20 -0
  12. {engin-0.0.7 → engin-0.0.8}/uv.lock +30 -29
  13. {engin-0.0.7 → engin-0.0.8}/.github/workflows/check.yaml +0 -0
  14. {engin-0.0.7 → engin-0.0.8}/.github/workflows/publish.yaml +0 -0
  15. {engin-0.0.7 → engin-0.0.8}/.gitignore +0 -0
  16. {engin-0.0.7 → engin-0.0.8}/.readthedocs.yaml +0 -0
  17. {engin-0.0.7 → engin-0.0.8}/LICENSE +0 -0
  18. {engin-0.0.7 → engin-0.0.8}/README.md +0 -0
  19. {engin-0.0.7 → engin-0.0.8}/docs/concepts/engin.md +0 -0
  20. {engin-0.0.7 → engin-0.0.8}/docs/concepts/invocations.md +0 -0
  21. {engin-0.0.7 → engin-0.0.8}/docs/concepts/lifecycle.md +0 -0
  22. {engin-0.0.7 → engin-0.0.8}/docs/concepts/providers.md +0 -0
  23. {engin-0.0.7 → engin-0.0.8}/docs/engin.md +0 -0
  24. {engin-0.0.7 → engin-0.0.8}/docs/guides/dependency_injection.md +0 -0
  25. {engin-0.0.7 → engin-0.0.8}/docs/index.md +0 -0
  26. {engin-0.0.7 → engin-0.0.8}/docs/js/readthedocs.js +0 -0
  27. {engin-0.0.7 → engin-0.0.8}/docs/overrides/main.html +0 -0
  28. {engin-0.0.7 → engin-0.0.8}/examples/__init__.py +0 -0
  29. {engin-0.0.7 → engin-0.0.8}/examples/asgi/__init__.py +0 -0
  30. {engin-0.0.7 → engin-0.0.8}/examples/asgi/app.py +0 -0
  31. {engin-0.0.7 → engin-0.0.8}/examples/asgi/common/__init__.py +0 -0
  32. {engin-0.0.7 → engin-0.0.8}/examples/asgi/common/db/__init__.py +0 -0
  33. {engin-0.0.7 → engin-0.0.8}/examples/asgi/common/db/adapaters/__init__.py +0 -0
  34. {engin-0.0.7 → engin-0.0.8}/examples/asgi/common/db/adapaters/memory.py +0 -0
  35. {engin-0.0.7 → engin-0.0.8}/examples/asgi/common/db/block.py +0 -0
  36. {engin-0.0.7 → engin-0.0.8}/examples/asgi/common/db/ports.py +0 -0
  37. {engin-0.0.7 → engin-0.0.8}/examples/asgi/common/starlette/__init__.py +0 -0
  38. {engin-0.0.7 → engin-0.0.8}/examples/asgi/common/starlette/endpoint.py +0 -0
  39. {engin-0.0.7 → engin-0.0.8}/examples/asgi/features/__init__.py +0 -0
  40. {engin-0.0.7 → engin-0.0.8}/examples/asgi/features/cats/__init__.py +0 -0
  41. {engin-0.0.7 → engin-0.0.8}/examples/asgi/features/cats/api/__init__.py +0 -0
  42. {engin-0.0.7 → engin-0.0.8}/examples/asgi/features/cats/api/get.py +0 -0
  43. {engin-0.0.7 → engin-0.0.8}/examples/asgi/features/cats/api/post.py +0 -0
  44. {engin-0.0.7 → engin-0.0.8}/examples/asgi/features/cats/block.py +0 -0
  45. {engin-0.0.7 → engin-0.0.8}/examples/asgi/features/cats/domain.py +0 -0
  46. {engin-0.0.7 → engin-0.0.8}/examples/asgi/main.py +0 -0
  47. {engin-0.0.7 → engin-0.0.8}/examples/fastapi/__init__.py +0 -0
  48. {engin-0.0.7 → engin-0.0.8}/examples/fastapi/app.py +0 -0
  49. {engin-0.0.7 → engin-0.0.8}/examples/fastapi/routes/__init__.py +0 -0
  50. {engin-0.0.7 → engin-0.0.8}/examples/fastapi/routes/cats/__init__.py +0 -0
  51. {engin-0.0.7 → engin-0.0.8}/examples/fastapi/routes/cats/adapters/__init__.py +0 -0
  52. {engin-0.0.7 → engin-0.0.8}/examples/fastapi/routes/cats/adapters/repository.py +0 -0
  53. {engin-0.0.7 → engin-0.0.8}/examples/fastapi/routes/cats/api.py +0 -0
  54. {engin-0.0.7 → engin-0.0.8}/examples/fastapi/routes/cats/block.py +0 -0
  55. {engin-0.0.7 → engin-0.0.8}/examples/fastapi/routes/cats/domain.py +0 -0
  56. {engin-0.0.7 → engin-0.0.8}/examples/fastapi/routes/cats/ports.py +0 -0
  57. {engin-0.0.7 → engin-0.0.8}/examples/simple/__init__.py +0 -0
  58. {engin-0.0.7 → engin-0.0.8}/mkdocs.yaml +0 -0
  59. {engin-0.0.7 → engin-0.0.8}/src/engin/__init__.py +0 -0
  60. {engin-0.0.7 → engin-0.0.8}/src/engin/_assembler.py +0 -0
  61. {engin-0.0.7 → engin-0.0.8}/src/engin/_block.py +0 -0
  62. {engin-0.0.7 → engin-0.0.8}/src/engin/_exceptions.py +0 -0
  63. {engin-0.0.7 → engin-0.0.8}/src/engin/_lifecycle.py +0 -0
  64. {engin-0.0.7 → engin-0.0.8}/src/engin/_type_utils.py +0 -0
  65. {engin-0.0.7 → engin-0.0.8}/src/engin/ext/__init__.py +0 -0
  66. {engin-0.0.7 → engin-0.0.8}/src/engin/ext/asgi.py +0 -0
  67. {engin-0.0.7 → engin-0.0.8}/src/engin/ext/fastapi.py +0 -0
  68. {engin-0.0.7 → engin-0.0.8}/src/engin/py.typed +0 -0
  69. {engin-0.0.7/tests → engin-0.0.8/src/engin/scripts}/__init__.py +0 -0
  70. {engin-0.0.7/tests/acceptance → engin-0.0.8/tests}/__init__.py +0 -0
  71. /engin-0.0.7/tests/conftest.py → /engin-0.0.8/tests/acceptance/__init__.py +0 -0
  72. {engin-0.0.7 → engin-0.0.8}/tests/acceptance/test_error_in_shutdown.py +0 -0
  73. {engin-0.0.7 → engin-0.0.8}/tests/acceptance/test_error_in_start_up.py +0 -0
  74. {engin-0.0.7 → engin-0.0.8}/tests/deps.py +0 -0
  75. {engin-0.0.7 → engin-0.0.8}/tests/test_assembler.py +0 -0
  76. {engin-0.0.7 → engin-0.0.8}/tests/test_dependencies.py +0 -0
  77. {engin-0.0.7 → engin-0.0.8}/tests/test_modules.py +0 -0
  78. {engin-0.0.7 → engin-0.0.8}/tests/test_utils.py +0 -0
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+
9
+ ## [0.0.8] - 2025-02-22
10
+
11
+ ### Added
12
+
13
+ - A package script, `engin-graph` for visualising the dependency graph.
14
+
15
+
8
16
  ## [0.0.7] - 2025-02-20
9
17
 
10
18
  ### Changed
@@ -1,8 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engin
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: An async-first modular application framework
5
+ Project-URL: Homepage, https://github.com/invokermain/engin
6
+ Project-URL: Documentation, https://engin.readthedocs.io/en/latest/
7
+ Project-URL: Repository, https://github.com/invokermain/engin.git
8
+ Project-URL: Changelog, https://github.com/invokermain/engin/blob/main/CHANGELOG.md
9
+ License-Expression: MIT
5
10
  License-File: LICENSE
11
+ Keywords: Application Framework,Dependency Injection
6
12
  Requires-Python: >=3.10
7
13
  Description-Content-Type: text/markdown
8
14
 
@@ -11,4 +11,6 @@ logging.basicConfig(level=logging.DEBUG)
11
11
 
12
12
  app = FastAPIEngin(AppBlock(), CatBlock(), Supply(AppConfig(debug=True)))
13
13
 
14
- uvicorn.run(app)
14
+
15
+ if __name__ == "__main__":
16
+ uvicorn.run(app)
@@ -19,4 +19,5 @@ async def main(http_client: AsyncClient) -> None:
19
19
 
20
20
  engin = Engin(Provide(new_httpx_client), Invoke(main))
21
21
 
22
- asyncio.run(engin.run())
22
+ if __name__ == "__main__":
23
+ asyncio.run(engin.run())
@@ -1,11 +1,19 @@
1
1
  [project]
2
2
  name = "engin"
3
- version = "0.0.7"
3
+ version = "0.0.8"
4
4
  description = "An async-first modular application framework"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
+ license = "MIT"
8
+ keywords = ["Dependency Injection", "Application Framework"]
7
9
  dependencies = []
8
10
 
11
+ [project.urls]
12
+ Homepage = "https://github.com/invokermain/engin"
13
+ Documentation = "https://engin.readthedocs.io/en/latest/"
14
+ Repository = "https://github.com/invokermain/engin.git"
15
+ Changelog = "https://github.com/invokermain/engin/blob/main/CHANGELOG.md"
16
+
9
17
  [build-system]
10
18
  requires = ["hatchling"]
11
19
  build-backend = "hatchling.build"
@@ -34,6 +42,10 @@ docs = [
34
42
  ]
35
43
 
36
44
 
45
+ [project.scripts]
46
+ engin-graph = "engin.scripts.graph:serve_graph"
47
+
48
+
37
49
  [tool.ruff]
38
50
  line-length = 95
39
51
  target-version = "py310"
@@ -54,7 +66,9 @@ ignore = [
54
66
  [tool.ruff.lint.per-file-ignores]
55
67
  "**/src/*" = ["PT"]
56
68
  "**/tests/*" = ["S", "ANN"]
69
+ # allow print statements in examples/scripts
57
70
  "**/examples/*" = ["T201"]
71
+ "**/scripts/*" = ["T201"]
58
72
 
59
73
 
60
74
  [tool.pytest.ini_options]
@@ -18,7 +18,6 @@ from engin._type_utils import TypeId, type_id_of
18
18
  P = ParamSpec("P")
19
19
  T = TypeVar("T")
20
20
  Func: TypeAlias = Callable[P, T]
21
- _SELF = object()
22
21
 
23
22
 
24
23
  def _noop(*args: Any, **kwargs: Any) -> None: ...
@@ -31,6 +30,10 @@ class Dependency(ABC, Generic[P, T]):
31
30
  self._signature = inspect.signature(self._func)
32
31
  self._block_name = block_name
33
32
 
33
+ @property
34
+ def module(self) -> str:
35
+ return self._func.__module__
36
+
34
37
  @property
35
38
  def block_name(self) -> str | None:
36
39
  return self._block_name
@@ -13,6 +13,7 @@ from engin import Entrypoint
13
13
  from engin._assembler import AssembledDependency, Assembler
14
14
  from engin._block import Block
15
15
  from engin._dependency import Dependency, Invoke, Provide, Supply
16
+ from engin._graph import DependencyGrapher, Node
16
17
  from engin._lifecycle import Lifecycle
17
18
  from engin._type_utils import TypeId
18
19
 
@@ -80,7 +81,6 @@ class Engin:
80
81
  Args:
81
82
  *options: an instance of Provide, Supply, Invoke, Entrypoint or a Block.
82
83
  """
83
-
84
84
  self._stop_requested_event = Event()
85
85
  self._stop_complete_event = Event()
86
86
  self._exit_stack: AsyncExitStack = AsyncExitStack()
@@ -95,8 +95,6 @@ class Engin:
95
95
  self._destruct_options(chain(self._LIB_OPTIONS, options))
96
96
  multi_providers = [p for multi in self._multiproviders.values() for p in multi]
97
97
  self._assembler = Assembler(chain(self._providers.values(), multi_providers))
98
- self._providers.clear()
99
- self._multiproviders.clear()
100
98
 
101
99
  @property
102
100
  def assembler(self) -> Assembler:
@@ -162,6 +160,10 @@ class Engin:
162
160
  return
163
161
  await self._stop_complete_event.wait()
164
162
 
163
+ def graph(self) -> list[Node]:
164
+ grapher = DependencyGrapher({**self._providers, **self._multiproviders})
165
+ return grapher.resolve(self._invocations)
166
+
165
167
  async def _shutdown(self) -> None:
166
168
  LOG.info("stopping engin")
167
169
  await self._exit_stack.aclose()
@@ -0,0 +1,39 @@
1
+ from collections.abc import Iterable
2
+ from typing import TypedDict
3
+
4
+ from engin._dependency import Dependency, Provide
5
+ from engin._type_utils import TypeId
6
+
7
+
8
+ class Node(TypedDict):
9
+ node: Dependency
10
+ parent: Dependency | None
11
+
12
+
13
+ class DependencyGrapher:
14
+ def __init__(self, providers: dict[TypeId, Provide | list[Provide]]) -> None:
15
+ self._providers: dict[TypeId, Provide | list[Provide]] = providers
16
+
17
+ def resolve(self, roots: Iterable[Dependency]) -> list[Node]:
18
+ seen: set[TypeId] = set()
19
+ nodes: list[Node] = []
20
+
21
+ for root in roots:
22
+ for parameter in root.parameter_types:
23
+ if parameter in seen:
24
+ continue
25
+
26
+ seen.add(parameter)
27
+ provider = self._providers[parameter]
28
+
29
+ # multiprovider
30
+ if isinstance(provider, list):
31
+ for p in provider:
32
+ nodes.append({"node": p, "parent": root})
33
+ nodes.extend(self.resolve([p]))
34
+ # single provider
35
+ else:
36
+ nodes.append({"node": provider, "parent": root})
37
+ nodes.extend(self.resolve([provider]))
38
+
39
+ return nodes
@@ -0,0 +1,122 @@
1
+ import importlib
2
+ import logging
3
+ import socketserver
4
+ import sys
5
+ import threading
6
+ from argparse import ArgumentParser
7
+ from http.server import BaseHTTPRequestHandler
8
+ from time import sleep
9
+ from typing import Any
10
+
11
+ from engin import Engin
12
+ from engin._dependency import Dependency, Provide
13
+
14
+ # mute logging from importing of files + engin's debug logging.
15
+ logging.disable()
16
+
17
+ args = ArgumentParser(
18
+ prog="engin-graph",
19
+ description="Creates a visualisation of your application's dependencies",
20
+ )
21
+ args.add_argument(
22
+ "-e", "--exclude", help="a list of packages or module to exclude", default=["engin"]
23
+ )
24
+ args.add_argument(
25
+ "app",
26
+ help=(
27
+ "the import path of your Engin instance, in the form "
28
+ "'package:application', e.g. 'app.main:engin'"
29
+ ),
30
+ )
31
+
32
+
33
+ def serve_graph() -> None:
34
+ # add cwd to path to enable local package imports
35
+ sys.path.insert(0, "")
36
+
37
+ parsed = args.parse_args()
38
+
39
+ app = parsed.app
40
+ excluded_modules = parsed.exclude
41
+
42
+ try:
43
+ module_name, engin_name = app.split(":", maxsplit=1)
44
+ except ValueError:
45
+ raise ValueError(
46
+ "Expected an argument of the form 'module:attribute', e.g. 'myapp:engin'"
47
+ ) from None
48
+
49
+ module = importlib.import_module(module_name)
50
+
51
+ try:
52
+ instance = getattr(module, engin_name)
53
+ except LookupError:
54
+ raise LookupError(f"Module '{module_name}' has no attribute '{engin_name}'") from None
55
+
56
+ if not isinstance(instance, Engin):
57
+ raise TypeError(f"'{app}' is not an Engin instance")
58
+
59
+ nodes = instance.graph()
60
+
61
+ # transform dependencies into mermaid syntax
62
+ dependencies = [
63
+ f"{_render_node(node['parent'])} --> {_render_node(node['node'])}"
64
+ for node in nodes
65
+ if node["parent"] is not None
66
+ and not _should_exclude(node["node"].module, excluded_modules)
67
+ ]
68
+
69
+ html = _GRAPH_HTML.replace("%%DATA%%", "\n".join(dependencies)).encode("utf8")
70
+
71
+ class Handler(BaseHTTPRequestHandler):
72
+ def do_GET(self) -> None:
73
+ self.send_response(200, "OK")
74
+ self.send_header("Content-type", "html")
75
+ self.end_headers()
76
+ self.wfile.write(html)
77
+
78
+ def log_message(self, format: str, *args: Any) -> None:
79
+ return
80
+
81
+ def _start_server() -> None:
82
+ with socketserver.TCPServer(("localhost", 8123), Handler) as httpd:
83
+ print("Serving dependency graph on http://localhost:8123")
84
+ httpd.serve_forever()
85
+
86
+ server_thread = threading.Thread(target=_start_server)
87
+ server_thread.daemon = True # Daemonize the thread so it exits when the main script exits
88
+ server_thread.start()
89
+
90
+ try:
91
+ sleep(10000)
92
+ except KeyboardInterrupt:
93
+ print("Exiting the server...")
94
+
95
+
96
+ def _render_node(node: Dependency) -> str:
97
+ if isinstance(node, Provide):
98
+ return str(node.return_type_id)
99
+ else:
100
+ return node.name
101
+
102
+
103
+ def _should_exclude(module: str, excluded: list[str]) -> bool:
104
+ return any(module.startswith(e) for e in excluded)
105
+
106
+
107
+ _GRAPH_HTML = """
108
+ <!doctype html>
109
+ <html lang="en">
110
+ <body>
111
+ <pre class="mermaid">
112
+ graph TD
113
+ %%DATA%%
114
+ </pre>
115
+ <script type="module">
116
+ import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
117
+ let config = { flowchart: { useMaxWidth: false, htmlLabels: true } };
118
+ mermaid.initialize(config);
119
+ </script>
120
+ </body>
121
+ </html>
122
+ """
File without changes
@@ -136,3 +136,23 @@ async def test_engin_with_lifecycle_using_run():
136
136
  await asyncio.gather(engin.run(), _stop_task())
137
137
  # lifecycle should have stopped by now
138
138
  assert state == 2
139
+
140
+
141
+ def test_engin_graph():
142
+ def a() -> A:
143
+ return A()
144
+
145
+ def b(_: A) -> B:
146
+ return B()
147
+
148
+ def c(_: B) -> C:
149
+ return C()
150
+
151
+ def main(c: C) -> None:
152
+ assert isinstance(c, C)
153
+
154
+ engin = Engin(Provide(a), Provide(b), Provide(c), Invoke(main))
155
+
156
+ graph = engin.graph()
157
+
158
+ assert len(graph) == 3
@@ -1,4 +1,5 @@
1
1
  version = 1
2
+ revision = 1
2
3
  requires-python = ">=3.10"
3
4
 
4
5
  [[package]]
@@ -127,7 +128,7 @@ wheels = [
127
128
 
128
129
  [[package]]
129
130
  name = "engin"
130
- version = "0.0.6"
131
+ version = "0.0.8"
131
132
  source = { editable = "." }
132
133
 
133
134
  [package.dev-dependencies]
@@ -414,7 +415,7 @@ wheels = [
414
415
 
415
416
  [[package]]
416
417
  name = "mkdocs-material"
417
- version = "9.6.4"
418
+ version = "9.6.5"
418
419
  source = { registry = "https://pypi.org/simple" }
419
420
  dependencies = [
420
421
  { name = "babel" },
@@ -429,9 +430,9 @@ dependencies = [
429
430
  { name = "regex" },
430
431
  { name = "requests" },
431
432
  ]
432
- sdist = { url = "https://files.pythonhosted.org/packages/9b/80/4efbd3df76c6c1ec27130b43662612f9033adc5a4166f1df2acb8dd6fb1b/mkdocs_material-9.6.4.tar.gz", hash = "sha256:4d1d35e1c1d3e15294cb7fa5d02e0abaee70d408f75027dc7be6e30fb32e6867", size = 3942628 }
433
+ sdist = { url = "https://files.pythonhosted.org/packages/38/4d/0a9f6f604f01eaa43df3b3b30b5218548efd7341913b302815585f48abb2/mkdocs_material-9.6.5.tar.gz", hash = "sha256:b714679a8c91b0ffe2188e11ed58c44d2523e9c2ae26a29cc652fa7478faa21f", size = 3946479 }
433
434
  wheels = [
434
- { url = "https://files.pythonhosted.org/packages/5b/a5/f3c0e86c1d28fe04f1b724700ff3dd8b3647c89df03a8e10c4bc6b4db1b8/mkdocs_material-9.6.4-py3-none-any.whl", hash = "sha256:414e8376551def6d644b8e6f77226022868532a792eb2c9accf52199009f568f", size = 8688727 },
435
+ { url = "https://files.pythonhosted.org/packages/3d/05/7d440b23454c0fc8cdba21f73ce23369eb16e7f7ee475fac3a4ad15ad5e0/mkdocs_material-9.6.5-py3-none-any.whl", hash = "sha256:aad3e6fb860c20870f75fb2a69ef901f1be727891e41adb60b753efcae19453b", size = 8695060 },
435
436
  ]
436
437
 
437
438
  [[package]]
@@ -468,7 +469,7 @@ python = [
468
469
 
469
470
  [[package]]
470
471
  name = "mkdocstrings-python"
471
- version = "1.15.0"
472
+ version = "1.16.1"
472
473
  source = { registry = "https://pypi.org/simple" }
473
474
  dependencies = [
474
475
  { name = "griffe" },
@@ -476,9 +477,9 @@ dependencies = [
476
477
  { name = "mkdocstrings" },
477
478
  { name = "typing-extensions", marker = "python_full_version < '3.11'" },
478
479
  ]
479
- sdist = { url = "https://files.pythonhosted.org/packages/28/5e/ea531f1798d6b614f87b7a1191f8bfda864767adecef3c75ec87f30e0a3d/mkdocstrings_python-1.15.0.tar.gz", hash = "sha256:2bfecbbe1252c67281408a6567d59545f4979931110f01ab625aa8c227c06edc", size = 422613 }
480
+ sdist = { url = "https://files.pythonhosted.org/packages/82/a4/3475fd03f3d566ca05872cec76a86d94ead23d99bbf6a89035b924a3e9b6/mkdocstrings_python-1.16.1.tar.gz", hash = "sha256:d7152d17da74d3616a0f17df5d2da771ecf7340518c158650e5a64a0a95973f4", size = 423399 }
480
481
  wheels = [
481
- { url = "https://files.pythonhosted.org/packages/6f/d7/1d35cce198f76e8ae4010a71ff5acabe8b75aeb35f8c3d920e175a6476ca/mkdocstrings_python-1.15.0-py3-none-any.whl", hash = "sha256:77aced1bb28840d7d3510f77353319eeb450961880d87f9c53fdab331ba0120d", size = 449068 },
482
+ { url = "https://files.pythonhosted.org/packages/00/f7/433201c48d4b59208dcbae6e1481febdf732ae20ecb2aee84a4ea142f043/mkdocstrings_python-1.16.1-py3-none-any.whl", hash = "sha256:b88ff6fc6a293cee9cb42313f1cba37a2c5cdf37bcc60b241ec7ab66b5d41b58", size = 449139 },
482
483
  ]
483
484
 
484
485
  [[package]]
@@ -687,15 +688,15 @@ wheels = [
687
688
 
688
689
  [[package]]
689
690
  name = "pydantic-settings"
690
- version = "2.7.1"
691
+ version = "2.8.0"
691
692
  source = { registry = "https://pypi.org/simple" }
692
693
  dependencies = [
693
694
  { name = "pydantic" },
694
695
  { name = "python-dotenv" },
695
696
  ]
696
- sdist = { url = "https://files.pythonhosted.org/packages/73/7b/c58a586cd7d9ac66d2ee4ba60ca2d241fa837c02bca9bea80a9a8c3d22a9/pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93", size = 79920 }
697
+ sdist = { url = "https://files.pythonhosted.org/packages/ca/a2/ad2511ede77bb424f3939e5148a56d968cdc6b1462620d24b2a1f4ab65b4/pydantic_settings-2.8.0.tar.gz", hash = "sha256:88e2ca28f6e68ea102c99c3c401d6c9078e68a5df600e97b43891c34e089500a", size = 83347 }
697
698
  wheels = [
698
- { url = "https://files.pythonhosted.org/packages/b4/46/93416fdae86d40879714f72956ac14df9c7b76f7d41a4d68aa9f71a0028b/pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd", size = 29718 },
699
+ { url = "https://files.pythonhosted.org/packages/c1/a9/3b9642025174bbe67e900785fb99c9bfe91ea584b0b7126ff99945c24a0e/pydantic_settings-2.8.0-py3-none-any.whl", hash = "sha256:c782c7dc3fb40e97b238e713c25d26f64314aece2e91abcff592fcac15f71820", size = 30746 },
699
700
  ]
700
701
 
701
702
  [[package]]
@@ -912,27 +913,27 @@ wheels = [
912
913
 
913
914
  [[package]]
914
915
  name = "ruff"
915
- version = "0.9.6"
916
+ version = "0.9.7"
916
917
  source = { registry = "https://pypi.org/simple" }
917
- sdist = { url = "https://files.pythonhosted.org/packages/2a/e1/e265aba384343dd8ddd3083f5e33536cd17e1566c41453a5517b5dd443be/ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9", size = 3639454 }
918
+ sdist = { url = "https://files.pythonhosted.org/packages/39/8b/a86c300359861b186f18359adf4437ac8e4c52e42daa9eedc731ef9d5b53/ruff-0.9.7.tar.gz", hash = "sha256:643757633417907510157b206e490c3aa11cab0c087c912f60e07fbafa87a4c6", size = 3669813 }
918
919
  wheels = [
919
- { url = "https://files.pythonhosted.org/packages/76/e3/3d2c022e687e18cf5d93d6bfa2722d46afc64eaa438c7fbbdd603b3597be/ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba", size = 11714128 },
920
- { url = "https://files.pythonhosted.org/packages/e1/22/aff073b70f95c052e5c58153cba735748c9e70107a77d03420d7850710a0/ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504", size = 11682539 },
921
- { url = "https://files.pythonhosted.org/packages/75/a7/f5b7390afd98a7918582a3d256cd3e78ba0a26165a467c1820084587cbf9/ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83", size = 11132512 },
922
- { url = "https://files.pythonhosted.org/packages/a6/e3/45de13ef65047fea2e33f7e573d848206e15c715e5cd56095589a7733d04/ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc", size = 11929275 },
923
- { url = "https://files.pythonhosted.org/packages/7d/f2/23d04cd6c43b2e641ab961ade8d0b5edb212ecebd112506188c91f2a6e6c/ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b", size = 11466502 },
924
- { url = "https://files.pythonhosted.org/packages/b5/6f/3a8cf166f2d7f1627dd2201e6cbc4cb81f8b7d58099348f0c1ff7b733792/ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e", size = 12676364 },
925
- { url = "https://files.pythonhosted.org/packages/f5/c4/db52e2189983c70114ff2b7e3997e48c8318af44fe83e1ce9517570a50c6/ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666", size = 13335518 },
926
- { url = "https://files.pythonhosted.org/packages/66/44/545f8a4d136830f08f4d24324e7db957c5374bf3a3f7a6c0bc7be4623a37/ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5", size = 12823287 },
927
- { url = "https://files.pythonhosted.org/packages/c5/26/8208ef9ee7431032c143649a9967c3ae1aae4257d95e6f8519f07309aa66/ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5", size = 14592374 },
928
- { url = "https://files.pythonhosted.org/packages/31/70/e917781e55ff39c5b5208bda384fd397ffd76605e68544d71a7e40944945/ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217", size = 12500173 },
929
- { url = "https://files.pythonhosted.org/packages/84/f5/e4ddee07660f5a9622a9c2b639afd8f3104988dc4f6ba0b73ffacffa9a8c/ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6", size = 11906555 },
930
- { url = "https://files.pythonhosted.org/packages/f1/2b/6ff2fe383667075eef8656b9892e73dd9b119b5e3add51298628b87f6429/ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897", size = 11538958 },
931
- { url = "https://files.pythonhosted.org/packages/3c/db/98e59e90de45d1eb46649151c10a062d5707b5b7f76f64eb1e29edf6ebb1/ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08", size = 12117247 },
932
- { url = "https://files.pythonhosted.org/packages/ec/bc/54e38f6d219013a9204a5a2015c09e7a8c36cedcd50a4b01ac69a550b9d9/ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656", size = 12554647 },
933
- { url = "https://files.pythonhosted.org/packages/a5/7d/7b461ab0e2404293c0627125bb70ac642c2e8d55bf590f6fce85f508f1b2/ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d", size = 9949214 },
934
- { url = "https://files.pythonhosted.org/packages/ee/30/c3cee10f915ed75a5c29c1e57311282d1a15855551a64795c1b2bbe5cf37/ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa", size = 10999914 },
935
- { url = "https://files.pythonhosted.org/packages/e8/a8/d71f44b93e3aa86ae232af1f2126ca7b95c0f515ec135462b3e1f351441c/ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a", size = 10177499 },
920
+ { url = "https://files.pythonhosted.org/packages/b1/f3/3a1d22973291226df4b4e2ff70196b926b6f910c488479adb0eeb42a0d7f/ruff-0.9.7-py3-none-linux_armv6l.whl", hash = "sha256:99d50def47305fe6f233eb8dabfd60047578ca87c9dcb235c9723ab1175180f4", size = 11774588 },
921
+ { url = "https://files.pythonhosted.org/packages/8e/c9/b881f4157b9b884f2994fd08ee92ae3663fb24e34b0372ac3af999aa7fc6/ruff-0.9.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d59105ae9c44152c3d40a9c40d6331a7acd1cdf5ef404fbe31178a77b174ea66", size = 11746848 },
922
+ { url = "https://files.pythonhosted.org/packages/14/89/2f546c133f73886ed50a3d449e6bf4af27d92d2f960a43a93d89353f0945/ruff-0.9.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f313b5800483770bd540cddac7c90fc46f895f427b7820f18fe1822697f1fec9", size = 11177525 },
923
+ { url = "https://files.pythonhosted.org/packages/d7/93/6b98f2c12bf28ab9def59c50c9c49508519c5b5cfecca6de871cf01237f6/ruff-0.9.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042ae32b41343888f59c0a4148f103208bf6b21c90118d51dc93a68366f4e903", size = 11996580 },
924
+ { url = "https://files.pythonhosted.org/packages/8e/3f/b3fcaf4f6d875e679ac2b71a72f6691a8128ea3cb7be07cbb249f477c061/ruff-0.9.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87862589373b33cc484b10831004e5e5ec47dc10d2b41ba770e837d4f429d721", size = 11525674 },
925
+ { url = "https://files.pythonhosted.org/packages/f0/48/33fbf18defb74d624535d5d22adcb09a64c9bbabfa755bc666189a6b2210/ruff-0.9.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a17e1e01bee0926d351a1ee9bc15c445beae888f90069a6192a07a84af544b6b", size = 12739151 },
926
+ { url = "https://files.pythonhosted.org/packages/63/b5/7e161080c5e19fa69495cbab7c00975ef8a90f3679caa6164921d7f52f4a/ruff-0.9.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7c1f880ac5b2cbebd58b8ebde57069a374865c73f3bf41f05fe7a179c1c8ef22", size = 13416128 },
927
+ { url = "https://files.pythonhosted.org/packages/4e/c8/b5e7d61fb1c1b26f271ac301ff6d9de5e4d9a9a63f67d732fa8f200f0c88/ruff-0.9.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e63fc20143c291cab2841dbb8260e96bafbe1ba13fd3d60d28be2c71e312da49", size = 12870858 },
928
+ { url = "https://files.pythonhosted.org/packages/da/cb/2a1a8e4e291a54d28259f8fc6a674cd5b8833e93852c7ef5de436d6ed729/ruff-0.9.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91ff963baed3e9a6a4eba2a02f4ca8eaa6eba1cc0521aec0987da8d62f53cbef", size = 14786046 },
929
+ { url = "https://files.pythonhosted.org/packages/ca/6c/c8f8a313be1943f333f376d79724260da5701426c0905762e3ddb389e3f4/ruff-0.9.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88362e3227c82f63eaebf0b2eff5b88990280fb1ecf7105523883ba8c3aaf6fb", size = 12550834 },
930
+ { url = "https://files.pythonhosted.org/packages/9d/ad/f70cf5e8e7c52a25e166bdc84c082163c9c6f82a073f654c321b4dff9660/ruff-0.9.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0372c5a90349f00212270421fe91874b866fd3626eb3b397ede06cd385f6f7e0", size = 11961307 },
931
+ { url = "https://files.pythonhosted.org/packages/52/d5/4f303ea94a5f4f454daf4d02671b1fbfe2a318b5fcd009f957466f936c50/ruff-0.9.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d76b8ab60e99e6424cd9d3d923274a1324aefce04f8ea537136b8398bbae0a62", size = 11612039 },
932
+ { url = "https://files.pythonhosted.org/packages/eb/c8/bd12a23a75603c704ce86723be0648ba3d4ecc2af07eecd2e9fa112f7e19/ruff-0.9.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0c439bdfc8983e1336577f00e09a4e7a78944fe01e4ea7fe616d00c3ec69a3d0", size = 12168177 },
933
+ { url = "https://files.pythonhosted.org/packages/cc/57/d648d4f73400fef047d62d464d1a14591f2e6b3d4a15e93e23a53c20705d/ruff-0.9.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:115d1f15e8fdd445a7b4dc9a30abae22de3f6bcabeb503964904471691ef7606", size = 12610122 },
934
+ { url = "https://files.pythonhosted.org/packages/49/79/acbc1edd03ac0e2a04ae2593555dbc9990b34090a9729a0c4c0cf20fb595/ruff-0.9.7-py3-none-win32.whl", hash = "sha256:e9ece95b7de5923cbf38893f066ed2872be2f2f477ba94f826c8defdd6ec6b7d", size = 9988751 },
935
+ { url = "https://files.pythonhosted.org/packages/6d/95/67153a838c6b6ba7a2401241fd8a00cd8c627a8e4a0491b8d853dedeffe0/ruff-0.9.7-py3-none-win_amd64.whl", hash = "sha256:3770fe52b9d691a15f0b87ada29c45324b2ace8f01200fb0c14845e499eb0c2c", size = 11002987 },
936
+ { url = "https://files.pythonhosted.org/packages/63/6a/aca01554949f3a401991dc32fe22837baeaccb8a0d868256cbb26a029778/ruff-0.9.7-py3-none-win_arm64.whl", hash = "sha256:b075a700b2533feb7a01130ff656a4ec0d5f340bb540ad98759b8401c32c2037", size = 10177763 },
936
937
  ]
937
938
 
938
939
  [[package]]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes