fred-oss 0.55.0__tar.gz → 0.56.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.56.0}/PKG-INFO +1 -1
  2. fred_oss-0.56.0/src/main/fred/cli/main.py +59 -0
  3. fred_oss-0.56.0/src/main/fred/rest/__init__.py +17 -0
  4. fred_oss-0.56.0/src/main/fred/rest/auth.py +23 -0
  5. fred_oss-0.56.0/src/main/fred/rest/config.py +32 -0
  6. fred_oss-0.56.0/src/main/fred/rest/router/catalog/default/__init__.py +10 -0
  7. fred_oss-0.56.0/src/main/fred/rest/router/catalog/default/_base.py +29 -0
  8. fred_oss-0.56.0/src/main/fred/rest/router/catalog/default/_example.py +31 -0
  9. fred_oss-0.56.0/src/main/fred/rest/router/catalog/default/catalog.py +20 -0
  10. fred_oss-0.56.0/src/main/fred/rest/router/catalog/interface.py +11 -0
  11. fred_oss-0.56.0/src/main/fred/rest/router/config.py +40 -0
  12. fred_oss-0.56.0/src/main/fred/rest/router/endpoint.py +94 -0
  13. fred_oss-0.56.0/src/main/fred/rest/router/interface.py +90 -0
  14. fred_oss-0.56.0/src/main/fred/rest/server.py +104 -0
  15. fred_oss-0.56.0/src/main/fred/rest/settings.py +46 -0
  16. fred_oss-0.56.0/src/main/fred/version +1 -0
  17. fred_oss-0.56.0/src/main/fred/worker/runner/rest/__init__.py +0 -0
  18. fred_oss-0.56.0/src/main/fred/worker/runner/rest/routers/__init__.py +0 -0
  19. {fred_oss-0.55.0 → fred_oss-0.56.0/src/main/fred_oss.egg-info}/PKG-INFO +1 -1
  20. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred_oss.egg-info/SOURCES.txt +15 -0
  21. fred_oss-0.55.0/src/main/fred/cli/main.py +0 -31
  22. fred_oss-0.55.0/src/main/fred/version +0 -1
  23. {fred_oss-0.55.0 → fred_oss-0.56.0}/MANIFEST.in +0 -0
  24. {fred_oss-0.55.0 → fred_oss-0.56.0}/NOTICE.txt +0 -0
  25. {fred_oss-0.55.0 → fred_oss-0.56.0}/README.md +0 -0
  26. {fred_oss-0.55.0 → fred_oss-0.56.0}/requirements.txt +0 -0
  27. {fred_oss-0.55.0 → fred_oss-0.56.0}/setup.cfg +0 -0
  28. {fred_oss-0.55.0 → fred_oss-0.56.0}/setup.py +0 -0
  29. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/cli/__init__.py +0 -0
  30. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/cli/__main__.py +0 -0
  31. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/cli/interface.py +0 -0
  32. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/__init__.py +0 -0
  33. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/comp/__init__.py +0 -0
  34. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/comp/_keyval.py +0 -0
  35. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/comp/_pubsub.py +0 -0
  36. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/comp/_queue.py +0 -0
  37. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/comp/catalog.py +0 -0
  38. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/comp/interface.py +0 -0
  39. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/__init__.py +0 -0
  40. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/_minio/__init__.py +0 -0
  41. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/_minio/policy/__init__.py +0 -0
  42. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/_minio/policy/builder.py +0 -0
  43. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/_minio/policy/catalog.py +0 -0
  44. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/_minio/policy/loader.py +0 -0
  45. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/_minio/policy/templates/public_ro.json +0 -0
  46. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/_minio/policy/templates/public_rw.json +0 -0
  47. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/_minio/pool.py +0 -0
  48. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/_minio/service.py +0 -0
  49. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/_redis.py +0 -0
  50. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/_stdlib.py +0 -0
  51. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/catalog.py +0 -0
  52. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/interface.py +0 -0
  53. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/dao/service/utils.py +0 -0
  54. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/future/__init__.py +0 -0
  55. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/future/callback/__init__.py +0 -0
  56. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/future/callback/_function.py +0 -0
  57. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/future/callback/catalog.py +0 -0
  58. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/future/callback/interface.py +0 -0
  59. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/future/impl.py +0 -0
  60. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/future/result.py +0 -0
  61. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/future/settings.py +0 -0
  62. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/future/utils.py +0 -0
  63. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/integrations/databricks/__init__.py +0 -0
  64. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/integrations/databricks/cli_ext.py +0 -0
  65. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/integrations/databricks/runtime.py +0 -0
  66. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/integrations/databricks/runtimes/16.4LTS.json +0 -0
  67. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/integrations/databricks/runtimes/__init__.py +0 -0
  68. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/integrations/databricks/runtimes/scanner.py +0 -0
  69. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/integrations/databricks/runtimes/sync.py +0 -0
  70. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/integrations/databricks/wrappers/__init__.py +0 -0
  71. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/integrations/databricks/wrappers/dbutils.py +0 -0
  72. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/integrations/runpod/__init__.py +0 -0
  73. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/integrations/runpod/cli_ext.py +0 -0
  74. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/integrations/runpod/helper.py +0 -0
  75. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/maturity.py +0 -0
  76. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/monad/__init__.py +0 -0
  77. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/monad/_either.py +0 -0
  78. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/monad/catalog.py +0 -0
  79. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/monad/interface.py +0 -0
  80. {fred_oss-0.55.0/src/main/fred/utils → fred_oss-0.56.0/src/main/fred/rest/router}/__init__.py +0 -0
  81. {fred_oss-0.55.0/src/main/fred/utils/imout → fred_oss-0.56.0/src/main/fred/rest/router/catalog}/__init__.py +0 -0
  82. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/settings.py +0 -0
  83. {fred_oss-0.55.0/src/main/fred/worker/runner/model → fred_oss-0.56.0/src/main/fred/utils}/__init__.py +0 -0
  84. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/utils/dateops.py +0 -0
  85. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/utils/imops.py +0 -0
  86. {fred_oss-0.55.0/src/main/fred/worker/runner/plugins → fred_oss-0.56.0/src/main/fred/utils/imout}/__init__.py +0 -0
  87. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/utils/imout/_filesystem.py +0 -0
  88. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/utils/imout/_minio.py +0 -0
  89. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/utils/imout/_string.py +0 -0
  90. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/utils/imout/catalog.py +0 -0
  91. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/utils/imout/interface.py +0 -0
  92. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/utils/mlops/__init__.py +0 -0
  93. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/utils/mlops/auto.py +0 -0
  94. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/utils/runtime.py +0 -0
  95. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/version.py +0 -0
  96. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/__init__.py +0 -0
  97. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/interface.py +0 -0
  98. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/__init__.py +0 -0
  99. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/backend.py +0 -0
  100. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/client.py +0 -0
  101. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/handler.py +0 -0
  102. {fred_oss-0.55.0/src/main/fred/worker/runner/rest → fred_oss-0.56.0/src/main/fred/worker/runner/model}/__init__.py +0 -0
  103. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/model/_handler.py +0 -0
  104. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/model/_item.py +0 -0
  105. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/model/_request.py +0 -0
  106. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/model/_runner_spec.py +0 -0
  107. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/model/catalog.py +0 -0
  108. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/model/interface.py +0 -0
  109. {fred_oss-0.55.0/src/main/fred/worker/runner/rest/routers → fred_oss-0.56.0/src/main/fred/worker/runner/plugins}/__init__.py +0 -0
  110. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/plugins/_local.py +0 -0
  111. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/plugins/_runpod.py +0 -0
  112. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/plugins/catalog.py +0 -0
  113. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/plugins/interface.py +0 -0
  114. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/rest/auth.py +0 -0
  115. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/rest/cli_ext.py +0 -0
  116. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/rest/routers/_runner.py +0 -0
  117. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/rest/routers/catalog.py +0 -0
  118. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/rest/routers/interface.py +0 -0
  119. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/rest/server.py +0 -0
  120. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/rest/settings.py +0 -0
  121. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/settings.py +0 -0
  122. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/signal.py +0 -0
  123. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/status.py +0 -0
  124. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/runner/utils.py +0 -0
  125. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred/worker/settings.py +0 -0
  126. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred_oss.egg-info/dependency_links.txt +0 -0
  127. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred_oss.egg-info/entry_points.txt +0 -0
  128. {fred_oss-0.55.0 → fred_oss-0.56.0}/src/main/fred_oss.egg-info/requires.txt +0 -0
  129. {fred_oss-0.55.0 → fred_oss-0.56.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.56.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
+ )
@@ -0,0 +1,23 @@
1
+ from fastapi import Security, HTTPException, status
2
+ from fastapi.security import APIKeyHeader
3
+
4
+ from fred.rest.settings import FRD_RESTAPI_TOKEN
5
+
6
+
7
+ async def verify_key(
8
+ api_key_header: str = Security(
9
+ APIKeyHeader(
10
+ name="X-API-Key",
11
+ auto_error=False
12
+ )
13
+ ),
14
+ ):
15
+ """
16
+ Verify the provided API key against the expected token.
17
+ Raises an HTTPException if the key is invalid or missing.
18
+ """
19
+ if api_key_header != FRD_RESTAPI_TOKEN:
20
+ raise HTTPException(
21
+ status_code=status.HTTP_401_UNAUTHORIZED,
22
+ detail="Invalid or missing API Key",
23
+ )
@@ -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,94 @@
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, other_type):
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
+ return self.function(other, **params)
91
+ return RouterEndpoint(
92
+ function=closure,
93
+ configs=self.configs
94
+ )
@@ -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
+ }
@@ -0,0 +1,104 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional
3
+
4
+ from fastapi import FastAPI, Depends
5
+
6
+ from fred.settings import (
7
+ get_environ_variable,
8
+ logger_manager,
9
+ )
10
+ from fred.rest.settings import (
11
+ FRD_RESTAPI_HOST,
12
+ FRD_RESTAPI_PORT,
13
+ FRD_RESTAPI_LOGLEVEL,
14
+ FRD_RESTAPI_DISABLE_AUTH,
15
+ )
16
+ from fred.rest.config import ServerRouterCatalogConfig
17
+ from fred.rest.auth import verify_key
18
+
19
+ logger = logger_manager.get_logger(name=__name__)
20
+
21
+
22
+ @dataclass(frozen=True, slots=True)
23
+ class FredServer:
24
+ app: FastAPI
25
+ include_routers: list[str]
26
+ exclude_routers: list[str]
27
+ router_classname: Optional[str] = None
28
+ router_classpath: Optional[str] = None
29
+
30
+ @classmethod
31
+ def auto(cls, **kwargs) -> "FredServer":
32
+ # Include routers by checking on keyword argument or environment variable
33
+ include_routers = kwargs.pop("include_routers", None) or get_environ_variable(
34
+ "FRD_RESTAPI_INCLUDE_ROUTERS",
35
+ default=""
36
+ )
37
+ if isinstance(include_routers, str):
38
+ include_routers = [
39
+ name.upper()
40
+ for router in include_routers.split(";")
41
+ if (name := router.strip())
42
+ ]
43
+ # Exclude routers by checking on keyword argument or environment variable
44
+ exclude_routers = kwargs.pop("exclude_routers", None) or get_environ_variable(
45
+ "FRD_RESTAPI_EXCLUDE_ROUTERS",
46
+ default=""
47
+ )
48
+ if isinstance(exclude_routers, str):
49
+ exclude_routers = [
50
+ name.upper()
51
+ for router in exclude_routers.split(";")
52
+ if (name := router.strip())
53
+ ]
54
+ # Auth dependencies setup
55
+ auth_dependency = [] if FRD_RESTAPI_DISABLE_AUTH else [
56
+ Depends(verify_key),
57
+ ]
58
+ kwargs["dependencies"] = kwargs.get("dependencies", []) + auth_dependency
59
+ # Create FastAPI app instance
60
+ app_instance = FastAPI(**kwargs)
61
+ router_classname = kwargs.pop("router_classname", None)
62
+ router_classpath = kwargs.pop("router_classpath", None)
63
+ return cls(
64
+ app=app_instance,
65
+ include_routers=include_routers,
66
+ exclude_routers=exclude_routers,
67
+ router_classname=router_classname,
68
+ router_classpath=router_classpath,
69
+ )
70
+
71
+ def __post_init__(self):
72
+ logger.info("Attempting to register routers...")
73
+ logger.info("Included routers candidates: %s", self.include_routers or "ALL")
74
+ logger.info("Excluded routers candidates: %s", self.exclude_routers or "NONE")
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()
81
+ if self.include_routers and name not in self.include_routers:
82
+ logger.info(f"Skipping router '{name}' as it's not in the include list.")
83
+ continue
84
+ if self.exclude_routers and name in self.exclude_routers:
85
+ logger.info(f"Skipping router '{name}' as it's in the exclude list.")
86
+ continue
87
+ logger.info(f"Registering router '{name}'.")
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)
93
+
94
+ def start(self, **kwargs):
95
+ import uvicorn
96
+
97
+ server_kwargs = {
98
+ "host": kwargs.pop("host", FRD_RESTAPI_HOST),
99
+ "port": int(kwargs.pop("port", FRD_RESTAPI_PORT)),
100
+ "log_level": FRD_RESTAPI_LOGLEVEL,
101
+ **kwargs,
102
+ }
103
+
104
+ uvicorn.run(self.app, **server_kwargs)
@@ -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.56.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fred-oss
3
- Version: 0.55.0
3
+ Version: 0.56.0
4
4
  Summary: FREDOSS
5
5
  Home-page: https://fred.fahera.mx
6
6
  Author: Fahera Research, Education, and Development
@@ -58,6 +58,21 @@ src/main/fred/monad/__init__.py
58
58
  src/main/fred/monad/_either.py
59
59
  src/main/fred/monad/catalog.py
60
60
  src/main/fred/monad/interface.py
61
+ src/main/fred/rest/__init__.py
62
+ src/main/fred/rest/auth.py
63
+ src/main/fred/rest/config.py
64
+ src/main/fred/rest/server.py
65
+ src/main/fred/rest/settings.py
66
+ src/main/fred/rest/router/__init__.py
67
+ src/main/fred/rest/router/config.py
68
+ src/main/fred/rest/router/endpoint.py
69
+ src/main/fred/rest/router/interface.py
70
+ src/main/fred/rest/router/catalog/__init__.py
71
+ src/main/fred/rest/router/catalog/interface.py
72
+ src/main/fred/rest/router/catalog/default/__init__.py
73
+ src/main/fred/rest/router/catalog/default/_base.py
74
+ src/main/fred/rest/router/catalog/default/_example.py
75
+ src/main/fred/rest/router/catalog/default/catalog.py
61
76
  src/main/fred/utils/__init__.py
62
77
  src/main/fred/utils/dateops.py
63
78
  src/main/fred/utils/imops.py
@@ -1,31 +0,0 @@
1
- from fred.version import version
2
- from fred.settings import logger_manager
3
- from fred.cli.interface import AbstractCLI
4
-
5
-
6
- logger = logger_manager.get_logger(name=__name__)
7
-
8
-
9
- class CLIExtensionGroups:
10
- """CLI Extensions providing access to various integrations by following a lazy loading pattern."""
11
-
12
- @property
13
- def databricks(self):
14
- from fred.integrations.databricks.cli_ext import DatabricksExt
15
- return DatabricksExt()
16
-
17
- @property
18
- def runpod(self):
19
- from fred.integrations.runpod.cli_ext import RunPodExt
20
- return RunPodExt()
21
-
22
- @property
23
- def runner_server(self):
24
- from fred.worker.runner.rest.cli_ext import RunnerServerExt
25
- return RunnerServerExt()
26
-
27
-
28
- class CLI(AbstractCLI, CLIExtensionGroups):
29
-
30
- def version(self) -> str:
31
- return version.value
@@ -1 +0,0 @@
1
- 0.55.0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes