fred-oss 0.55.0__tar.gz → 0.57.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.
Files changed (129) hide show
  1. {fred_oss-0.55.0/src/main/fred_oss.egg-info → fred_oss-0.57.0}/PKG-INFO +1 -1
  2. fred_oss-0.57.0/src/main/fred/cli/main.py +59 -0
  3. fred_oss-0.57.0/src/main/fred/rest/__init__.py +17 -0
  4. {fred_oss-0.55.0/src/main/fred/worker/runner → fred_oss-0.57.0/src/main/fred}/rest/auth.py +2 -1
  5. fred_oss-0.57.0/src/main/fred/rest/config.py +32 -0
  6. fred_oss-0.57.0/src/main/fred/rest/router/catalog/default/__init__.py +10 -0
  7. fred_oss-0.57.0/src/main/fred/rest/router/catalog/default/_base.py +29 -0
  8. fred_oss-0.57.0/src/main/fred/rest/router/catalog/default/_example.py +31 -0
  9. fred_oss-0.57.0/src/main/fred/rest/router/catalog/default/catalog.py +20 -0
  10. fred_oss-0.57.0/src/main/fred/rest/router/catalog/interface.py +11 -0
  11. fred_oss-0.57.0/src/main/fred/rest/router/config.py +40 -0
  12. fred_oss-0.57.0/src/main/fred/rest/router/endpoint.py +97 -0
  13. fred_oss-0.57.0/src/main/fred/rest/router/interface.py +90 -0
  14. {fred_oss-0.55.0/src/main/fred/worker/runner → fred_oss-0.57.0/src/main/fred}/rest/server.py +36 -17
  15. fred_oss-0.57.0/src/main/fred/rest/settings.py +46 -0
  16. fred_oss-0.57.0/src/main/fred/version +1 -0
  17. fred_oss-0.57.0/src/main/fred/worker/runner/rest/__init__.py +0 -0
  18. fred_oss-0.57.0/src/main/fred/worker/runner/rest/router/__init__.py +0 -0
  19. fred_oss-0.57.0/src/main/fred/worker/runner/rest/router/_base.py +22 -0
  20. {fred_oss-0.55.0/src/main/fred/worker/runner/rest/routers → fred_oss-0.57.0/src/main/fred/worker/runner/rest/router}/_runner.py +51 -62
  21. fred_oss-0.57.0/src/main/fred/worker/runner/rest/router/catalog.py +11 -0
  22. {fred_oss-0.55.0 → fred_oss-0.57.0/src/main/fred_oss.egg-info}/PKG-INFO +1 -1
  23. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred_oss.egg-info/SOURCES.txt +19 -8
  24. fred_oss-0.55.0/src/main/fred/cli/main.py +0 -31
  25. fred_oss-0.55.0/src/main/fred/version +0 -1
  26. fred_oss-0.55.0/src/main/fred/worker/runner/rest/cli_ext.py +0 -32
  27. fred_oss-0.55.0/src/main/fred/worker/runner/rest/routers/catalog.py +0 -18
  28. fred_oss-0.55.0/src/main/fred/worker/runner/rest/routers/interface.py +0 -59
  29. fred_oss-0.55.0/src/main/fred/worker/runner/rest/settings.py +0 -21
  30. {fred_oss-0.55.0 → fred_oss-0.57.0}/MANIFEST.in +0 -0
  31. {fred_oss-0.55.0 → fred_oss-0.57.0}/NOTICE.txt +0 -0
  32. {fred_oss-0.55.0 → fred_oss-0.57.0}/README.md +0 -0
  33. {fred_oss-0.55.0 → fred_oss-0.57.0}/requirements.txt +0 -0
  34. {fred_oss-0.55.0 → fred_oss-0.57.0}/setup.cfg +0 -0
  35. {fred_oss-0.55.0 → fred_oss-0.57.0}/setup.py +0 -0
  36. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/cli/__init__.py +0 -0
  37. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/cli/__main__.py +0 -0
  38. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/cli/interface.py +0 -0
  39. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/__init__.py +0 -0
  40. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/comp/__init__.py +0 -0
  41. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/comp/_keyval.py +0 -0
  42. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/comp/_pubsub.py +0 -0
  43. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/comp/_queue.py +0 -0
  44. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/comp/catalog.py +0 -0
  45. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/comp/interface.py +0 -0
  46. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/__init__.py +0 -0
  47. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/_minio/__init__.py +0 -0
  48. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/_minio/policy/__init__.py +0 -0
  49. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/_minio/policy/builder.py +0 -0
  50. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/_minio/policy/catalog.py +0 -0
  51. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/_minio/policy/loader.py +0 -0
  52. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/_minio/policy/templates/public_ro.json +0 -0
  53. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/_minio/policy/templates/public_rw.json +0 -0
  54. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/_minio/pool.py +0 -0
  55. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/_minio/service.py +0 -0
  56. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/_redis.py +0 -0
  57. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/_stdlib.py +0 -0
  58. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/catalog.py +0 -0
  59. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/interface.py +0 -0
  60. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/dao/service/utils.py +0 -0
  61. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/future/__init__.py +0 -0
  62. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/future/callback/__init__.py +0 -0
  63. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/future/callback/_function.py +0 -0
  64. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/future/callback/catalog.py +0 -0
  65. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/future/callback/interface.py +0 -0
  66. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/future/impl.py +0 -0
  67. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/future/result.py +0 -0
  68. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/future/settings.py +0 -0
  69. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/future/utils.py +0 -0
  70. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/integrations/databricks/__init__.py +0 -0
  71. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/integrations/databricks/cli_ext.py +0 -0
  72. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/integrations/databricks/runtime.py +0 -0
  73. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/integrations/databricks/runtimes/16.4LTS.json +0 -0
  74. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/integrations/databricks/runtimes/__init__.py +0 -0
  75. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/integrations/databricks/runtimes/scanner.py +0 -0
  76. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/integrations/databricks/runtimes/sync.py +0 -0
  77. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/integrations/databricks/wrappers/__init__.py +0 -0
  78. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/integrations/databricks/wrappers/dbutils.py +0 -0
  79. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/integrations/runpod/__init__.py +0 -0
  80. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/integrations/runpod/cli_ext.py +0 -0
  81. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/integrations/runpod/helper.py +0 -0
  82. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/maturity.py +0 -0
  83. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/monad/__init__.py +0 -0
  84. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/monad/_either.py +0 -0
  85. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/monad/catalog.py +0 -0
  86. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/monad/interface.py +0 -0
  87. {fred_oss-0.55.0/src/main/fred/utils → fred_oss-0.57.0/src/main/fred/rest/router}/__init__.py +0 -0
  88. {fred_oss-0.55.0/src/main/fred/utils/imout → fred_oss-0.57.0/src/main/fred/rest/router/catalog}/__init__.py +0 -0
  89. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/settings.py +0 -0
  90. {fred_oss-0.55.0/src/main/fred/worker/runner/model → fred_oss-0.57.0/src/main/fred/utils}/__init__.py +0 -0
  91. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/utils/dateops.py +0 -0
  92. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/utils/imops.py +0 -0
  93. {fred_oss-0.55.0/src/main/fred/worker/runner/plugins → fred_oss-0.57.0/src/main/fred/utils/imout}/__init__.py +0 -0
  94. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/utils/imout/_filesystem.py +0 -0
  95. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/utils/imout/_minio.py +0 -0
  96. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/utils/imout/_string.py +0 -0
  97. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/utils/imout/catalog.py +0 -0
  98. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/utils/imout/interface.py +0 -0
  99. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/utils/mlops/__init__.py +0 -0
  100. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/utils/mlops/auto.py +0 -0
  101. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/utils/runtime.py +0 -0
  102. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/version.py +0 -0
  103. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/__init__.py +0 -0
  104. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/interface.py +0 -0
  105. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/__init__.py +0 -0
  106. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/backend.py +0 -0
  107. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/client.py +0 -0
  108. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/handler.py +0 -0
  109. {fred_oss-0.55.0/src/main/fred/worker/runner/rest → fred_oss-0.57.0/src/main/fred/worker/runner/model}/__init__.py +0 -0
  110. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/model/_handler.py +0 -0
  111. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/model/_item.py +0 -0
  112. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/model/_request.py +0 -0
  113. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/model/_runner_spec.py +0 -0
  114. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/model/catalog.py +0 -0
  115. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/model/interface.py +0 -0
  116. {fred_oss-0.55.0/src/main/fred/worker/runner/rest/routers → fred_oss-0.57.0/src/main/fred/worker/runner/plugins}/__init__.py +0 -0
  117. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/plugins/_local.py +0 -0
  118. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/plugins/_runpod.py +0 -0
  119. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/plugins/catalog.py +0 -0
  120. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/plugins/interface.py +0 -0
  121. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/settings.py +0 -0
  122. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/signal.py +0 -0
  123. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/status.py +0 -0
  124. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/runner/utils.py +0 -0
  125. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred/worker/settings.py +0 -0
  126. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred_oss.egg-info/dependency_links.txt +0 -0
  127. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred_oss.egg-info/entry_points.txt +0 -0
  128. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred_oss.egg-info/requires.txt +0 -0
  129. {fred_oss-0.55.0 → fred_oss-0.57.0}/src/main/fred_oss.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fred-oss
3
- Version: 0.55.0
3
+ Version: 0.57.0
4
4
  Summary: FREDOSS
5
5
  Home-page: https://fred.fahera.mx
6
6
  Author: Fahera Research, Education, and Development
@@ -0,0 +1,59 @@
1
+ from typing import Optional
2
+
3
+ from fred.version import version
4
+ from fred.settings import logger_manager
5
+ from fred.cli.interface import AbstractCLI
6
+
7
+
8
+ logger = logger_manager.get_logger(name=__name__)
9
+
10
+
11
+ class CLIExtensionGroups:
12
+ """CLI Extensions providing access to various integrations by following a lazy loading pattern."""
13
+
14
+ @property
15
+ def databricks(self):
16
+ from fred.integrations.databricks.cli_ext import DatabricksExt
17
+ return DatabricksExt()
18
+
19
+ @property
20
+ def runpod(self):
21
+ from fred.integrations.runpod.cli_ext import RunPodExt
22
+ return RunPodExt()
23
+
24
+ @property
25
+ def runner_server(self):
26
+ from fred.worker.runner.rest.cli_ext import RunnerServerExt
27
+ return RunnerServerExt()
28
+
29
+
30
+ class CLI(AbstractCLI, CLIExtensionGroups):
31
+
32
+ def version(self) -> str:
33
+ return version.value
34
+
35
+ def serve(
36
+ self,
37
+ classname: Optional[str] = None,
38
+ classpath: Optional[str] = None,
39
+ include_routers: Optional[list[str]] = None,
40
+ exclude_routers: Optional[list[str]] = None,
41
+ fastapi_configs: Optional[dict] = None,
42
+ server_configs: Optional[dict] = None,
43
+ ):
44
+ from fred.rest.server import FredServer
45
+
46
+ include_routers = include_routers or []
47
+ exclude_routers = exclude_routers or []
48
+ fastapi_configs = fastapi_configs or {}
49
+ server_configs = server_configs or {}
50
+
51
+ logger.info("Starting the Fred-REST Server...")
52
+ server = FredServer.auto(
53
+ include_routers=include_routers,
54
+ exclude_routers=exclude_routers,
55
+ router_classname=classname,
56
+ router_classpath=classpath,
57
+ **fastapi_configs,
58
+ )
59
+ server.start(**server_configs)
@@ -0,0 +1,17 @@
1
+ """
2
+ [Fred-REST] Fred's Simple Extensible REST-API Server
3
+ ========================================================
4
+ This module provides a simple and extensible REST-API server implementation using FastAPI.
5
+ It includes configurations for authentication, router management, and server startup.
6
+ """
7
+ from fred.maturity import Maturity, MaturityLevel
8
+
9
+
10
+ module_maturity = Maturity(
11
+ level=MaturityLevel.ALPHA,
12
+ reference=__name__,
13
+ message=(
14
+ "Fred-REST implementation is in early development "
15
+ "and therefore currently with incomplete and unstable features."
16
+ )
17
+ )
@@ -1,7 +1,8 @@
1
1
  from fastapi import Security, HTTPException, status
2
2
  from fastapi.security import APIKeyHeader
3
3
 
4
- from fred.worker.runner.rest.settings import FRD_RESTAPI_TOKEN
4
+ from fred.rest.settings import FRD_RESTAPI_TOKEN
5
+
5
6
 
6
7
  async def verify_key(
7
8
  api_key_header: str = Security(
@@ -0,0 +1,32 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional
3
+
4
+ from fred.rest.router.catalog.interface import RouterCatalogInterface
5
+ from fred.rest.settings import (
6
+ FRD_RESTAPI_ROUTERCATALOG_CLASSPATH,
7
+ FRD_RESTAPI_ROUTERCATALOG_CLASSNAME,
8
+ )
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class ServerRouterCatalogConfig:
12
+ classname: str
13
+ classpath: str
14
+
15
+ @classmethod
16
+ def auto(cls, classname: Optional[str] = None, classpath: Optional[str] = None) -> "ServerRouterCatalogConfig":
17
+ return cls(
18
+ classname=classname or FRD_RESTAPI_ROUTERCATALOG_CLASSNAME,
19
+ classpath=classpath or FRD_RESTAPI_ROUTERCATALOG_CLASSPATH,
20
+ )
21
+
22
+ @property
23
+ def catalog(self) -> type[RouterCatalogInterface]:
24
+ import importlib
25
+
26
+ module = importlib.import_module(self.classpath)
27
+ catalog_class = getattr(module, self.classname, None)
28
+ if catalog_class is None:
29
+ raise ImportError(f"Could not find class '{self.classname}' in module '{self.classpath}'")
30
+ if not issubclass(catalog_class, RouterCatalogInterface):
31
+ raise TypeError(f"Class '{self.classname}' is not a subclass of RouterCatalogInterface")
32
+ return catalog_class
@@ -0,0 +1,10 @@
1
+ from fred.rest.router.catalog.default.catalog import RouterCatalog
2
+ from fred.settings import logger_manager
3
+
4
+ logger = logger_manager.get_logger(name=__name__)
5
+
6
+
7
+ logger.warning(
8
+ "You are using the default router catalog intended for demonstration purposes only. "
9
+ "For production use, please implement a custom router catalog."
10
+ )
@@ -0,0 +1,29 @@
1
+ from fred.rest.router.interface import RouterInterfaceMixin
2
+ from fred.rest.router.endpoint import RouterEndpointAnnotation
3
+
4
+
5
+ class RouterBaseMixin(RouterInterfaceMixin):
6
+
7
+ @RouterEndpointAnnotation.set(
8
+ path="/",
9
+ methods=["GET"],
10
+ summary="Base endpoint",
11
+ description="A simple base endpoint to check service availability."
12
+ )
13
+ def base(self, include_telemetry: bool = False, **kwargs) -> dict:
14
+ if include_telemetry:
15
+ from fred.utils.runtime import RuntimeProfilingSnapshot
16
+ kwargs["telemetry"] = RuntimeProfilingSnapshot.auto().to_dict()
17
+ return {
18
+ "ok": True,
19
+ **kwargs
20
+ }
21
+
22
+ @RouterEndpointAnnotation.set(
23
+ path="/passthrough",
24
+ methods=["POST"],
25
+ summary="Passthrough POST endpoint",
26
+ description="An endpoint that returns all received parameters.",
27
+ )
28
+ def passthrough(self, **kwargs) -> dict:
29
+ return kwargs
@@ -0,0 +1,31 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional
3
+
4
+ from fred.rest.router.interface import RouterInterfaceMixin
5
+ from fred.rest.router.endpoint import RouterEndpointAnnotation
6
+
7
+
8
+ class RouterExampleMixin(RouterInterfaceMixin):
9
+
10
+ @RouterEndpointAnnotation.set(
11
+ path="/ping",
12
+ methods=["GET"],
13
+ summary="Ping endpoint",
14
+ description="A simple ping endpoint to check service availability.",
15
+ )
16
+ def ping(self, pong: Optional[str] = None, **kwargs) -> dict:
17
+ from fred.utils.dateops import datetime_utcnow
18
+
19
+ return {
20
+ "ping_time": datetime_utcnow().isoformat(),
21
+ "ping_response": pong or "pong",
22
+ }
23
+
24
+ @RouterEndpointAnnotation.set(
25
+ path="/passthrough",
26
+ methods=["GET"],
27
+ summary="Passthrough GET endpoint",
28
+ description="An endpoint that returns all received parameters.",
29
+ )
30
+ def passthrough(self, **kwargs) -> dict:
31
+ return kwargs
@@ -0,0 +1,20 @@
1
+ import enum
2
+
3
+ from fred.rest.router.config import RouterConfig
4
+ from fred.rest.router.catalog.default._base import RouterBaseMixin
5
+ from fred.rest.router.catalog.default._example import RouterExampleMixin
6
+
7
+ from fred.rest.router.catalog.interface import RouterCatalogInterface
8
+
9
+
10
+ class RouterCatalog(RouterCatalogInterface, enum.Enum):
11
+ BASE = RouterConfig.auto(prefix="")(apply=RouterBaseMixin)
12
+ EXAMPLE = RouterConfig.auto(prefix="/example")(apply=RouterExampleMixin)
13
+
14
+ def get_kwargs(self) -> dict:
15
+ match self:
16
+ case RouterCatalog.EXAMPLE:
17
+ # Disable the backend for the example router
18
+ return {"disregard_backend": True}
19
+ case _:
20
+ return {}
@@ -0,0 +1,11 @@
1
+ from fred.rest.router.interface import RouterInterface
2
+
3
+
4
+ class RouterCatalogInterface:
5
+
6
+ def auto(self, **kwargs) -> RouterInterface:
7
+ return self.value.reference.auto(**kwargs)
8
+
9
+ def get_kwargs(self) -> dict:
10
+ # This method can be overridden to provide specific kwargs for router initialization
11
+ return {}
@@ -0,0 +1,40 @@
1
+ from dataclasses import dataclass
2
+
3
+ from fred.rest.router.interface import RouterInterface, RouterInterfaceMixin
4
+
5
+
6
+ @dataclass(frozen=True, slots=True)
7
+ class RouterConfig:
8
+ prefix: str
9
+ other: dict
10
+
11
+ @classmethod
12
+ def auto(cls, prefix: str = "", **kwargs) -> "RouterConfig":
13
+ return cls(
14
+ prefix=prefix,
15
+ other=kwargs,
16
+ )
17
+
18
+ def get_applied_configs(self, router_class: type[RouterInterface]) -> "AppliedRouterConfig":
19
+ return AppliedRouterConfig(
20
+ reference=router_class,
21
+ config=self,
22
+ )
23
+
24
+ def __call__(self, apply: type[RouterInterfaceMixin]) -> "AppliedRouterConfig":
25
+ router_classname = f"Router{apply.__name__}"
26
+ return self.get_applied_configs(
27
+ router_class=type(router_classname, (RouterInterface, apply, ), {})
28
+ )
29
+
30
+ def get_configs(self) -> dict:
31
+ return {
32
+ "prefix": self.prefix,
33
+ **self.other,
34
+ }
35
+
36
+
37
+ @dataclass(frozen=True, slots=True)
38
+ class AppliedRouterConfig:
39
+ reference: type[RouterInterface]
40
+ config: RouterConfig
@@ -0,0 +1,97 @@
1
+ import os
2
+ from dataclasses import dataclass, field
3
+ from typing import Callable, Optional
4
+
5
+ from fastapi import Request
6
+
7
+ from fred.settings import logger_manager
8
+
9
+ logger = logger_manager.get_logger(name=__name__)
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class RouterEndpointConfig:
14
+ path: Optional[str] = None
15
+ methods: list[str] = field(default_factory=lambda: ["GET"])
16
+ configs: dict = field(default_factory=dict)
17
+
18
+ @classmethod
19
+ def auto(cls, **kwargs) -> "RouterEndpointConfig":
20
+ return cls(
21
+ path=kwargs.pop("path", None),
22
+ methods=(
23
+ kwargs.pop("methods", None)
24
+ or ["GET"]
25
+ ),
26
+ configs=kwargs
27
+ )
28
+
29
+
30
+ @dataclass(frozen=True, slots=False)
31
+ class RouterEndpoint:
32
+ function: Callable
33
+ configs: RouterEndpointConfig
34
+
35
+ def __call__(self, *args, **kwargs):
36
+ return self.function(*args, **kwargs)
37
+
38
+ def get_path(self, overwrite: Optional[str] = None) -> str:
39
+ fname = self.function.__name__
40
+ return (
41
+ overwrite
42
+ or self.configs.path
43
+ or logger.debug(f"Missing 'path' definition, defaulting to function name: {fname}")
44
+ or os.path.join("/", fname)
45
+ )
46
+
47
+ def route_config(self, path: Optional[str] = None) -> dict:
48
+ return {
49
+ "path": self.get_path(overwrite=path),
50
+ "methods": self.configs.methods,
51
+ **self.configs.configs,
52
+ }
53
+
54
+ # Here are a couple of reference to understand why this class is needed:
55
+ # https://stackoverflow.com/questions/2365701/decorating-class-methods-how-to-pass-the-instance-to-the-decorator/3296318#3296318
56
+ # https://stackoverflow.com/questions/75409483/why-do-i-get-missing-required-argument-self-when-using-a-decorator-written-as
57
+ @dataclass(frozen=True, slots=True)
58
+ class RouterEndpointAnnotation(RouterEndpoint):
59
+
60
+ @classmethod
61
+ def set(cls, **kwargs) -> Callable:
62
+ config = RouterEndpointConfig.auto(**kwargs)
63
+ return lambda function: cls(function=function, configs=config)
64
+
65
+ def __get__(self, other_instance, other_class) -> RouterEndpoint:
66
+ # Create a partial function that binds the instance (other) to the method.
67
+ # This method was originally implemented using the following approaches:
68
+ # - function = functools.partial(self.function, other)
69
+ # - function = lambda payload: self.function(other, **payload)
70
+ # - function = functools.wraps(self.function)(lambda *args, **kwargs: self.function(other, *args, **kwargs))
71
+ # However, these approaches did not work well with FastAPI's async handling and request parsing
72
+ # since the dynamic lambda attributes were not being recognized properly by FastAPI.
73
+ # The current implementation defines an async closure that correctly calls the inner function
74
+ # and retrieves the parameters directly from the Request object.
75
+ # For more info on how to use the request object, see:
76
+ # - https://fastapi.tiangolo.com/advanced/using-request-directly/#use-the-request-object-directly
77
+ # - https://stackoverflow.com/questions/67636088/how-to-access-request-object-in-router-function-using-fastapi
78
+ # - https://www.starlette.dev/requests/
79
+ async def closure(request: Request):
80
+ params = {
81
+ **request.headers,
82
+ **request.path_params,
83
+ **request.query_params,
84
+ }
85
+ try:
86
+ if request.method in ("POST", "PUT", "PATCH"):
87
+ params.update(await request.json())
88
+ except Exception:
89
+ pass
90
+ # Since the annotation is usually applied to methods within a class,
91
+ # in most cases the 'other_instance' will be 'None'; therefore using 'other_class'
92
+ # should allow accessing the shared-class level state (e.g., runner_backend).
93
+ return self.function(other_class, **params)
94
+ return RouterEndpoint(
95
+ function=closure,
96
+ configs=self.configs
97
+ )
@@ -0,0 +1,90 @@
1
+ from dataclasses import dataclass
2
+ from typing import Callable, Optional
3
+
4
+ from fred.worker.runner.backend import RunnerBackend
5
+ from fred.worker.runner.settings import FRD_RUNNER_BACKEND
6
+ from fred.rest.router.endpoint import RouterEndpoint
7
+ from fred.settings import logger_manager
8
+
9
+ from fastapi import APIRouter
10
+
11
+ logger = logger_manager.get_logger(name=__name__)
12
+
13
+
14
+ class RouterInterfaceMixin:
15
+ """Base class for router interfaces that require a backend service."""
16
+ runner_backend: RunnerBackend
17
+
18
+
19
+ @dataclass(frozen=True, slots=True)
20
+ class RouterInterface(RouterInterfaceMixin):
21
+ router: APIRouter
22
+
23
+ @classmethod
24
+ def set_backend(
25
+ cls,
26
+ service_name: Optional[str] = None,
27
+ disable_runner_reuse: bool = False,
28
+ **kwargs
29
+ ) -> type["RouterInterface"]:
30
+ runner_backend = kwargs.pop("runner_backend", None)
31
+ if not disable_runner_reuse and runner_backend is None:
32
+ logger.warning(
33
+ "Runner reuse is enabled but no existing backend was provided; "
34
+ "a new backend will be created."
35
+ )
36
+ disable_runner_reuse = True
37
+ if disable_runner_reuse:
38
+ runner_backend = RunnerBackend.auto(
39
+ service_name=service_name or FRD_RUNNER_BACKEND,
40
+ **kwargs,
41
+ )
42
+ if runner_backend is None:
43
+ raise ValueError("Runner backend could not be determined or created.")
44
+ return type(
45
+ "RouterInterfaceWithBackend",
46
+ (cls, ),
47
+ {
48
+ "runner_backend": runner_backend
49
+ }
50
+ )
51
+
52
+ @classmethod
53
+ def auto(
54
+ cls,
55
+ service_name: Optional[str] = None,
56
+ router: Optional[APIRouter] = None,
57
+ disregard_backend: bool = False,
58
+ disregard_endpoint_setup: bool = False,
59
+ **kwargs,
60
+ ) -> "RouterInterface":
61
+ subcls = cls if disregard_backend else cls.set_backend(
62
+ service_name=service_name,
63
+ runner_backend=getattr(cls, "runner_backend", None),
64
+ **kwargs
65
+ )
66
+ instance = subcls(router=router or APIRouter())
67
+ if disregard_endpoint_setup:
68
+ return instance
69
+ for endpoint in subcls.endpoint_definitions():
70
+ instance.register_endpoint(endpoint)
71
+ return instance
72
+
73
+ def register_endpoint(self, endpoint: RouterEndpoint, path: Optional[str] = None):
74
+ route_config = endpoint.route_config(path=path)
75
+ self.router.add_api_route(endpoint=endpoint.function, **route_config)
76
+
77
+ @classmethod
78
+ def endpoint_definitions(cls) -> list[RouterEndpoint]:
79
+ return [
80
+ obj
81
+ for attr in dir(cls)
82
+ if isinstance((obj := getattr(cls, attr)), RouterEndpoint)
83
+ ]
84
+
85
+ @classmethod
86
+ def endpoint_definition_mapping(cls) -> dict[str, RouterEndpoint]:
87
+ return {
88
+ endpoint.get_path(): endpoint
89
+ for endpoint in cls.endpoint_definitions()
90
+ }
@@ -1,29 +1,37 @@
1
1
  from dataclasses import dataclass
2
+ from typing import Optional
2
3
 
3
4
  from fastapi import FastAPI, Depends
4
5
 
5
- from fred.settings import logger_manager, get_environ_variable
6
- from fred.worker.runner.rest.settings import (
6
+ from fred.settings import (
7
+ get_environ_variable,
8
+ logger_manager,
9
+ )
10
+ from fred.rest.settings import (
7
11
  FRD_RESTAPI_HOST,
8
12
  FRD_RESTAPI_PORT,
13
+ FRD_RESTAPI_LOGLEVEL,
14
+ FRD_RESTAPI_DISABLE_AUTH,
9
15
  )
10
- from fred.worker.runner.rest.routers.catalog import RouterCatalog
11
- from fred.worker.runner.rest.auth import verify_key
16
+ from fred.rest.config import ServerRouterCatalogConfig
17
+ from fred.rest.auth import verify_key
12
18
 
13
19
  logger = logger_manager.get_logger(name=__name__)
14
20
 
15
21
 
16
22
  @dataclass(frozen=True, slots=True)
17
- class RunnerServer:
23
+ class FredServer:
18
24
  app: FastAPI
19
25
  include_routers: list[str]
20
26
  exclude_routers: list[str]
27
+ router_classname: Optional[str] = None
28
+ router_classpath: Optional[str] = None
21
29
 
22
30
  @classmethod
23
- def auto(cls, **kwargs) -> "RunnerServer":
31
+ def auto(cls, **kwargs) -> "FredServer":
24
32
  # Include routers by checking on keyword argument or environment variable
25
33
  include_routers = kwargs.pop("include_routers", None) or get_environ_variable(
26
- "FRD_RUNNER_API_INCLUDE_ROUTERS",
34
+ "FRD_RESTAPI_INCLUDE_ROUTERS",
27
35
  default=""
28
36
  )
29
37
  if isinstance(include_routers, str):
@@ -34,7 +42,7 @@ class RunnerServer:
34
42
  ]
35
43
  # Exclude routers by checking on keyword argument or environment variable
36
44
  exclude_routers = kwargs.pop("exclude_routers", None) or get_environ_variable(
37
- "FRD_RUNNER_API_EXCLUDE_ROUTERS",
45
+ "FRD_RESTAPI_EXCLUDE_ROUTERS",
38
46
  default=""
39
47
  )
40
48
  if isinstance(exclude_routers, str):
@@ -43,23 +51,33 @@ class RunnerServer:
43
51
  for router in exclude_routers.split(";")
44
52
  if (name := router.strip())
45
53
  ]
46
- # Create FastAPI instance
47
- kwargs["dependencies"] = kwargs.get("dependencies", []) + [
54
+ # Auth dependencies setup
55
+ auth_dependency = [] if FRD_RESTAPI_DISABLE_AUTH else [
48
56
  Depends(verify_key),
49
57
  ]
58
+ kwargs["dependencies"] = kwargs.get("dependencies", []) + auth_dependency
59
+ # Create FastAPI app instance
50
60
  app_instance = FastAPI(**kwargs)
61
+ router_classname = kwargs.pop("router_classname", None)
62
+ router_classpath = kwargs.pop("router_classpath", None)
51
63
  return cls(
52
64
  app=app_instance,
53
65
  include_routers=include_routers,
54
66
  exclude_routers=exclude_routers,
67
+ router_classname=router_classname,
68
+ router_classpath=router_classpath,
55
69
  )
56
70
 
57
71
  def __post_init__(self):
58
72
  logger.info("Attempting to register routers...")
59
73
  logger.info("Included routers candidates: %s", self.include_routers or "ALL")
60
74
  logger.info("Excluded routers candidates: %s", self.exclude_routers or "NONE")
61
- for router in RouterCatalog:
62
- name = router.name.upper()
75
+ srcc = ServerRouterCatalogConfig.auto(
76
+ classname=self.router_classname,
77
+ classpath=self.router_classpath,
78
+ )
79
+ for router_spec in srcc.catalog:
80
+ name = router_spec.name.upper()
63
81
  if self.include_routers and name not in self.include_routers:
64
82
  logger.info(f"Skipping router '{name}' as it's not in the include list.")
65
83
  continue
@@ -67,10 +85,11 @@ class RunnerServer:
67
85
  logger.info(f"Skipping router '{name}' as it's in the exclude list.")
68
86
  continue
69
87
  logger.info(f"Registering router '{name}'.")
70
- self.app.include_router(
71
- router.get_router_instance(),
72
- **router.get_router_configs(),
73
- )
88
+ kwargs = router_spec.get_kwargs()
89
+ router_instance = router_spec.value.reference.auto(**kwargs)
90
+ router_configs = router_spec.value.config.get_configs()
91
+ # Register the router in the FastAPI app
92
+ self.app.include_router(router_instance.router, **router_configs)
74
93
 
75
94
  def start(self, **kwargs):
76
95
  import uvicorn
@@ -78,7 +97,7 @@ class RunnerServer:
78
97
  server_kwargs = {
79
98
  "host": kwargs.pop("host", FRD_RESTAPI_HOST),
80
99
  "port": int(kwargs.pop("port", FRD_RESTAPI_PORT)),
81
- "log_level": "info",
100
+ "log_level": FRD_RESTAPI_LOGLEVEL,
82
101
  **kwargs,
83
102
  }
84
103
 
@@ -0,0 +1,46 @@
1
+ from fred.settings import get_environ_variable, logger_manager
2
+
3
+ logger = logger_manager.get_logger(name=__name__)
4
+
5
+
6
+ FRD_RESTAPI_HOST = get_environ_variable(
7
+ "FRD_RESTAPI_HOST",
8
+ default="0.0.0.0"
9
+ )
10
+ FRD_RESTAPI_PORT = int(get_environ_variable(
11
+ "FRD_RESTAPI_PORT",
12
+ default=8000
13
+ ))
14
+
15
+ FRD_RESTAPI_LOGLEVEL = get_environ_variable(
16
+ "FRD_RESTAPI_LOGLEVEL",
17
+ default="info"
18
+ ).lower()
19
+
20
+ FRD_RESTAPI_DISABLE_AUTH = get_environ_variable(
21
+ "FRD_RESTAPI_DISABLE_AUTH",
22
+ default="false"
23
+ ).lower() in ("1", "true", "yes", "on")
24
+
25
+ FRD_RESTAPI_TOKEN = get_environ_variable(
26
+ "FRD_RESTAPI_TOKEN",
27
+ default=None
28
+ )
29
+ if not FRD_RESTAPI_TOKEN:
30
+ logger.warning("FRD_RESTAPI_TOKEN not found in environment; using default token 'changeme'.")
31
+ FRD_RESTAPI_TOKEN = "changeme"
32
+
33
+ FRD_RESTAPI_EXCLUDE_BUILTIN_ROUTERS = get_environ_variable(
34
+ "FRD_RESTAPI_EXCLUDE_BUILTIN_ROUTERS",
35
+ default="false",
36
+ ).lower() in ("1", "true", "yes", "on")
37
+
38
+ FRD_RESTAPI_ROUTERCATALOG_CLASSNAME = get_environ_variable(
39
+ "FRD_RESTAPI_ROUTERCATALOG_CLASSNAME",
40
+ default="RouterCatalog",
41
+ )
42
+
43
+ FRD_RESTAPI_ROUTERCATALOG_CLASSPATH = get_environ_variable(
44
+ "FRD_RESTAPI_ROUTERCATALOG_CLASSPATH",
45
+ default="fred.rest.router.catalog.default",
46
+ )
@@ -0,0 +1 @@
1
+ 0.57.0
@@ -0,0 +1,22 @@
1
+ from dataclasses import dataclass
2
+
3
+ from fred.rest.router.interface import RouterInterfaceMixin
4
+ from fred.rest.router.endpoint import RouterEndpointAnnotation
5
+
6
+ @dataclass(frozen=True, slots=True)
7
+ class RouterBaseMixin(RouterInterfaceMixin):
8
+
9
+ @RouterEndpointAnnotation.set(
10
+ path="/",
11
+ methods=["GET"],
12
+ summary="Base endpoint",
13
+ description="A simple base endpoint to check service availability."
14
+ )
15
+ def base(self, include_telemetry: bool = False, **kwargs) -> dict:
16
+ if include_telemetry:
17
+ from fred.utils.runtime import RuntimeProfilingSnapshot
18
+ kwargs["telemetry"] = RuntimeProfilingSnapshot.auto().to_dict()
19
+ return {
20
+ "ok": True,
21
+ **kwargs
22
+ }