jac-scale 0.1.1__py3-none-any.whl
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.
- jac_scale/__init__.py +0 -0
- jac_scale/abstractions/config/app_config.jac +30 -0
- jac_scale/abstractions/config/base_config.jac +26 -0
- jac_scale/abstractions/database_provider.jac +51 -0
- jac_scale/abstractions/deployment_target.jac +64 -0
- jac_scale/abstractions/image_registry.jac +54 -0
- jac_scale/abstractions/logger.jac +20 -0
- jac_scale/abstractions/models/deployment_result.jac +27 -0
- jac_scale/abstractions/models/resource_status.jac +38 -0
- jac_scale/config_loader.jac +31 -0
- jac_scale/context.jac +14 -0
- jac_scale/factories/database_factory.jac +43 -0
- jac_scale/factories/deployment_factory.jac +43 -0
- jac_scale/factories/registry_factory.jac +32 -0
- jac_scale/factories/utility_factory.jac +34 -0
- jac_scale/impl/config_loader.impl.jac +131 -0
- jac_scale/impl/context.impl.jac +24 -0
- jac_scale/impl/memory_hierarchy.main.impl.jac +63 -0
- jac_scale/impl/memory_hierarchy.mongo.impl.jac +239 -0
- jac_scale/impl/memory_hierarchy.redis.impl.jac +186 -0
- jac_scale/impl/serve.impl.jac +1785 -0
- jac_scale/jserver/__init__.py +0 -0
- jac_scale/jserver/impl/jfast_api.impl.jac +731 -0
- jac_scale/jserver/impl/jserver.impl.jac +79 -0
- jac_scale/jserver/jfast_api.jac +162 -0
- jac_scale/jserver/jserver.jac +101 -0
- jac_scale/memory_hierarchy.jac +138 -0
- jac_scale/plugin.jac +218 -0
- jac_scale/plugin_config.jac +175 -0
- jac_scale/providers/database/kubernetes_mongo.jac +137 -0
- jac_scale/providers/database/kubernetes_redis.jac +110 -0
- jac_scale/providers/registry/dockerhub.jac +64 -0
- jac_scale/serve.jac +118 -0
- jac_scale/targets/kubernetes/kubernetes_config.jac +215 -0
- jac_scale/targets/kubernetes/kubernetes_target.jac +841 -0
- jac_scale/targets/kubernetes/utils/kubernetes_utils.impl.jac +519 -0
- jac_scale/targets/kubernetes/utils/kubernetes_utils.jac +85 -0
- jac_scale/tests/__init__.py +0 -0
- jac_scale/tests/conftest.py +29 -0
- jac_scale/tests/fixtures/test_api.jac +159 -0
- jac_scale/tests/fixtures/todo_app.jac +68 -0
- jac_scale/tests/test_abstractions.py +88 -0
- jac_scale/tests/test_deploy_k8s.py +265 -0
- jac_scale/tests/test_examples.py +484 -0
- jac_scale/tests/test_factories.py +149 -0
- jac_scale/tests/test_file_upload.py +444 -0
- jac_scale/tests/test_k8s_utils.py +156 -0
- jac_scale/tests/test_memory_hierarchy.py +247 -0
- jac_scale/tests/test_serve.py +1835 -0
- jac_scale/tests/test_sso.py +711 -0
- jac_scale/utilities/loggers/standard_logger.jac +40 -0
- jac_scale/utils.jac +16 -0
- jac_scale-0.1.1.dist-info/METADATA +658 -0
- jac_scale-0.1.1.dist-info/RECORD +57 -0
- jac_scale-0.1.1.dist-info/WHEEL +5 -0
- jac_scale-0.1.1.dist-info/entry_points.txt +3 -0
- jac_scale-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Handle execution of a DELETE endpoint."""
|
|
2
|
+
|
|
3
|
+
impl JServer._delete(self: JServer, endpoint: JEndPoint) -> 'JServer[T]' {
|
|
4
|
+
;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
"""Handle execution of a PATCH endpoint."""
|
|
8
|
+
impl JServer._patch(self: JServer, endpoint: JEndPoint) -> 'JServer[T]' {
|
|
9
|
+
;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
"""Handle execution of a PUT endpoint."""
|
|
13
|
+
impl JServer._put(self: JServer, endpoint: JEndPoint) -> 'JServer[T]' {
|
|
14
|
+
;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
"""Handle execution of a POST endpoint."""
|
|
18
|
+
impl JServer._post(self: JServer, endpoint: JEndPoint) -> 'JServer[T]' {
|
|
19
|
+
;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
"""Handle execution of a GET endpoint."""
|
|
23
|
+
impl JServer._get(self: JServer, endpoint: JEndPoint) -> 'JServer[T]' {
|
|
24
|
+
;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
Execute the provided endpoints by calling the appropriate HTTP method handlers.
|
|
29
|
+
|
|
30
|
+
This method iterates through the endpoint list and calls the appropriate
|
|
31
|
+
method (get, post, put, patch, delete) based on each endpoint's HTTP method.
|
|
32
|
+
"""
|
|
33
|
+
impl JServer.execute(self: JServer) -> None {
|
|
34
|
+
for endpoint in self._endpoints {
|
|
35
|
+
if (endpoint.method == HTTPMethod.GET) {
|
|
36
|
+
self._get(endpoint);
|
|
37
|
+
} elif (endpoint.method == HTTPMethod.POST) {
|
|
38
|
+
self._post(endpoint);
|
|
39
|
+
} elif (endpoint.method == HTTPMethod.PUT) {
|
|
40
|
+
self._put(endpoint);
|
|
41
|
+
} elif (endpoint.method == HTTPMethod.PATCH) {
|
|
42
|
+
self._patch(endpoint);
|
|
43
|
+
} elif (endpoint.method == HTTPMethod.DELETE) {
|
|
44
|
+
self._delete(endpoint);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
Add a single endpoint to the server implementation.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
endpoint (JEndPoint): The endpoint to add
|
|
54
|
+
"""
|
|
55
|
+
impl JServer.add_endpoint(self: JServer, endpoint: JEndPoint) -> None {
|
|
56
|
+
self._endpoints.append(endpoint);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
Return the list of registered endpoints.
|
|
61
|
+
This method should return only the endpoints that have been registered
|
|
62
|
+
with this server implementation, not create new ones.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
List[JEndPoint]: List of registered endpoint definitions
|
|
66
|
+
"""
|
|
67
|
+
impl JServer.get_endpoints(self: JServer) -> list[JEndPoint] {
|
|
68
|
+
return self._endpoints;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
Initialize the server with a list of endpoints.
|
|
73
|
+
Args:
|
|
74
|
+
end_points (list[JEndPoint]): List of endpoint definitions to register
|
|
75
|
+
"""
|
|
76
|
+
impl JServer.init(self: JServer, end_points: list[JEndPoint]) -> None {
|
|
77
|
+
super.init();
|
|
78
|
+
self._endpoints = end_points;
|
|
79
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JFastApiServer: A FastAPI Implementation of JServer
|
|
3
|
+
|
|
4
|
+
This module provides a FastAPI-specific implementation of the JServer abstract base class.
|
|
5
|
+
It handles endpoint registration with FastAPI applications and provides all the FastAPI-specific
|
|
6
|
+
functionality like parameter injection, response model generation, and route creation.
|
|
7
|
+
|
|
8
|
+
Key Components:
|
|
9
|
+
- JFastApiServer: FastAPI implementation of JServer
|
|
10
|
+
- create_app(): Creates a basic FastAPI application for demonstration
|
|
11
|
+
|
|
12
|
+
Advanced Features:
|
|
13
|
+
- Parameter injection with type conversion
|
|
14
|
+
- Response model generation from JSON schema
|
|
15
|
+
- Support for async/sync callback functions
|
|
16
|
+
- Automatic OpenAPI documentation generation
|
|
17
|
+
- Integration with JAC pass execution patterns
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import inspect;
|
|
21
|
+
import logging;
|
|
22
|
+
import uvicorn;
|
|
23
|
+
import from collections.abc { Callable }
|
|
24
|
+
import from typing { Any, Optional, TypeAlias, get_type_hints }
|
|
25
|
+
import from fastapi {
|
|
26
|
+
Body,
|
|
27
|
+
FastAPI,
|
|
28
|
+
File,
|
|
29
|
+
Form,
|
|
30
|
+
Header,
|
|
31
|
+
HTTPException,
|
|
32
|
+
Path,
|
|
33
|
+
Query,
|
|
34
|
+
Request,
|
|
35
|
+
UploadFile
|
|
36
|
+
}
|
|
37
|
+
import from fastapi.responses { Response, JSONResponse }
|
|
38
|
+
import from pydantic { BaseModel, Field, create_model }
|
|
39
|
+
import from .jserver { APIParameter, HTTPMethod, JEndPoint, JServer, ParameterType }
|
|
40
|
+
import from jaclang.runtimelib.transport { TransportResponse }
|
|
41
|
+
|
|
42
|
+
glob EndpointResponse: TypeAlias = Response | BaseModel | <>dict[(str, object)] | <>list[
|
|
43
|
+
object
|
|
44
|
+
] | str | <>bytes | None;
|
|
45
|
+
|
|
46
|
+
"""Base protocol for parameter handlers."""
|
|
47
|
+
obj ParameterHandler {
|
|
48
|
+
has type_converter: JFastApiServer;
|
|
49
|
+
|
|
50
|
+
"""Generate parameter string for FastAPI function signature."""
|
|
51
|
+
def generate_param_string(param: APIParameter) -> str;
|
|
52
|
+
|
|
53
|
+
"""Get the actual Python type from parameter type string."""
|
|
54
|
+
def get_type(param: APIParameter) -> type[Any] {
|
|
55
|
+
return self.type_converter._get_python_type(param.data_type);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
"""Get the type name for code generation."""
|
|
59
|
+
def get_type_name(param: APIParameter) -> str {
|
|
60
|
+
return self.get_type(param).__name__;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
"""Handler for PATH parameters."""
|
|
65
|
+
obj PathParameterHandler(ParameterHandler) {
|
|
66
|
+
def generate_param_string(param: APIParameter) -> str;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
"""Handler for QUERY parameters."""
|
|
70
|
+
obj QueryParameterHandler(ParameterHandler) {
|
|
71
|
+
def generate_param_string(param: APIParameter) -> str;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
"""Handler for HEADER parameters."""
|
|
75
|
+
obj HeaderParameterHandler(ParameterHandler) {
|
|
76
|
+
def generate_param_string(param: APIParameter) -> str;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
"""Handler for FILE parameters."""
|
|
80
|
+
obj FileParameterHandler(ParameterHandler) {
|
|
81
|
+
def generate_param_string(param: APIParameter) -> str;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
"""Handler for BODY parameters with files (multipart/form-data)."""
|
|
85
|
+
obj FormBodyParameterHandler(ParameterHandler) {
|
|
86
|
+
def generate_param_string(param: APIParameter) -> str;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
"""Handler for BODY parameters without files (JSON)."""
|
|
90
|
+
obj JSONBodyParameterHandler(ParameterHandler) {
|
|
91
|
+
has body_params: list[APIParameter];
|
|
92
|
+
|
|
93
|
+
"""Generate Pydantic model for JSON body."""
|
|
94
|
+
def generate_body_model -> (type[BaseModel] | None);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
JFastApiServer: FastAPI implementation of JServer for programmatic endpoint creation.
|
|
99
|
+
|
|
100
|
+
JFastApiServer provides FastAPI-specific implementation of the JServer interface,
|
|
101
|
+
handling endpoint registration with FastAPI applications.
|
|
102
|
+
|
|
103
|
+
This class implements the JServer interface by:
|
|
104
|
+
- Storing registered endpoints internally
|
|
105
|
+
- Implementing HTTP method handlers (_get, _post, _put, _patch, _delete)
|
|
106
|
+
- Providing FastAPI app instance for server execution
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
>>> # Create server with endpoints
|
|
110
|
+
>>> endpoints = [
|
|
111
|
+
... JEndPoint(HTTPMethod.GET, "/users", get_users_callback),
|
|
112
|
+
... JEndPoint(HTTPMethod.POST, "/users", create_user_callback)
|
|
113
|
+
... ]
|
|
114
|
+
>>> server = JFastApiServer(endpoints)
|
|
115
|
+
>>>
|
|
116
|
+
>>> # Execute to create FastAPI routes
|
|
117
|
+
>>> server.execute()
|
|
118
|
+
>>> app = server.create_server()
|
|
119
|
+
|
|
120
|
+
Attributes:
|
|
121
|
+
app (FastAPI): The underlying FastAPI application instance
|
|
122
|
+
"""
|
|
123
|
+
class JFastApiServer(JServer[FastAPI]) {
|
|
124
|
+
def init(
|
|
125
|
+
self: JFastApiServer,
|
|
126
|
+
endpoints: (list[JEndPoint] | None) = None,
|
|
127
|
+
app: (FastAPI | None) = None
|
|
128
|
+
) -> None;
|
|
129
|
+
|
|
130
|
+
def _get(self: JFastApiServer, endpoint: JEndPoint) -> 'JFastApiServer';
|
|
131
|
+
def _post(self: JFastApiServer, endpoint: JEndPoint) -> 'JFastApiServer';
|
|
132
|
+
def _put(self: JFastApiServer, endpoint: JEndPoint) -> 'JFastApiServer';
|
|
133
|
+
def _patch(self: JFastApiServer, endpoint: JEndPoint) -> 'JFastApiServer';
|
|
134
|
+
def _delete(self: JFastApiServer, endpoint: JEndPoint) -> 'JFastApiServer';
|
|
135
|
+
def _route_priority(
|
|
136
|
+
self: JFastApiServer, endpoint: JEndPoint
|
|
137
|
+
) -> tuple[int, int, int, str];
|
|
138
|
+
|
|
139
|
+
def execute(self: JFastApiServer) -> None;
|
|
140
|
+
def create_server(self: JFastApiServer) -> FastAPI;
|
|
141
|
+
def _create_fastapi_route(
|
|
142
|
+
self: JFastApiServer, method: HTTPMethod, endpoint: JEndPoint
|
|
143
|
+
) -> None;
|
|
144
|
+
|
|
145
|
+
def _get_default_status_code(self: JFastApiServer, method: HTTPMethod) -> int;
|
|
146
|
+
def _create_endpoint_function(
|
|
147
|
+
self: JFastApiServer,
|
|
148
|
+
callback: Callable[(..., Any)],
|
|
149
|
+
parameters: list[APIParameter],
|
|
150
|
+
dependencies: list[Any]
|
|
151
|
+
) -> Callable[..., Any];
|
|
152
|
+
|
|
153
|
+
def _get_python_type(self: JFastApiServer, type_string: str) -> type[Any];
|
|
154
|
+
def _create_response_model(
|
|
155
|
+
self: JFastApiServer, response_config: (dict[(str, Any)] | None) = None
|
|
156
|
+
) -> (type[BaseModel] | None);
|
|
157
|
+
|
|
158
|
+
def get_app(self: JFastApiServer) -> FastAPI;
|
|
159
|
+
def run_server(
|
|
160
|
+
self: JFastApiServer, host: str = '0.0.0.0', port: int = 8000
|
|
161
|
+
) -> None;
|
|
162
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import from abc { ABC, abstractmethod }
|
|
2
|
+
import from collections.abc { Callable }
|
|
3
|
+
import from dataclasses { dataclass }
|
|
4
|
+
import from typing { Any, Generic, TypeVar }
|
|
5
|
+
import from pydantic { BaseModel }
|
|
6
|
+
import from enum { StrEnum }
|
|
7
|
+
|
|
8
|
+
glob T = TypeVar('T');
|
|
9
|
+
|
|
10
|
+
class HTTPMethod(StrEnum) {
|
|
11
|
+
has GET: str = 'GET',
|
|
12
|
+
POST: str = 'POST',
|
|
13
|
+
PUT: str = 'PUT',
|
|
14
|
+
PATCH: str = 'PATCH',
|
|
15
|
+
DELETE: str = 'DELETE';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class ParameterType(StrEnum) {
|
|
19
|
+
has QUERY: str = 'query',
|
|
20
|
+
PATH: str = 'path',
|
|
21
|
+
BODY: str = 'body',
|
|
22
|
+
HEADER: str = 'header',
|
|
23
|
+
FILE: str = 'file';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
obj APIParameter {
|
|
27
|
+
has name: str,
|
|
28
|
+
<>type: ParameterType = ParameterType.QUERY,
|
|
29
|
+
data_type: str = 'str',
|
|
30
|
+
required: bool = True,
|
|
31
|
+
<>default: Any = None,
|
|
32
|
+
description: str = '';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
Data class representing a single API endpoint.
|
|
37
|
+
This class provides a clean representation of an endpoint configuration,
|
|
38
|
+
including its method, path, callback function, parameters, and response model.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
method (HTTPMethod): The HTTP method for the endpoint (GET, POST, etc.)
|
|
42
|
+
path (str): The URL path for the endpoint
|
|
43
|
+
callback (Callable): The function to be called when the endpoint is accessed
|
|
44
|
+
parameters (list[APIParameter] | None): List of parameters for the endpoint
|
|
45
|
+
response_model (type[BaseModel] | None): Pydantic model for the response
|
|
46
|
+
tags (list[str] | None): Tags for categorizing the endpoint
|
|
47
|
+
summary (str | None): Short summary of the endpoint
|
|
48
|
+
description (str | None): Detailed description of the endpoint
|
|
49
|
+
"""
|
|
50
|
+
obj JEndPoint {
|
|
51
|
+
has method: HTTPMethod,
|
|
52
|
+
path: str,
|
|
53
|
+
callback: Callable[(..., Any)],
|
|
54
|
+
parameters: (list[APIParameter] | None) = None,
|
|
55
|
+
response_model: (type[BaseModel] | None) = None,
|
|
56
|
+
tags: (list[str] | None) = None,
|
|
57
|
+
summary: (str | None) = None,
|
|
58
|
+
description: (str | None) = None;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
"""Abstract base class for server implementations."""
|
|
62
|
+
class JServer(ABC, Generic[T]) {
|
|
63
|
+
def init(self: JServer, end_points: list[JEndPoint]) -> None;
|
|
64
|
+
def get_endpoints(self: JServer) -> list[JEndPoint];
|
|
65
|
+
def add_endpoint(self: JServer, endpoint: JEndPoint) -> None;
|
|
66
|
+
def execute(self: JServer) -> None;
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def _get(self: JServer, endpoint: JEndPoint) -> 'JServer[T]';
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def _post(self: JServer, endpoint: JEndPoint) -> 'JServer[T]';
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def _put(self: JServer, endpoint: JEndPoint) -> 'JServer[T]';
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def _patch(self: JServer, endpoint: JEndPoint) -> 'JServer[T]';
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def _delete(self: JServer, endpoint: JEndPoint) -> 'JServer[T]';
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
Create a complete server with all endpoints registered.
|
|
84
|
+
|
|
85
|
+
This is a convenience method that gets all endpoints and executes them
|
|
86
|
+
to create a fully configured server. The return type depends on the
|
|
87
|
+
concrete implementation.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
app (Optional[FastAPI]): Optional FastAPI instance to use (ignored in base implementation)
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Any: Implementation-specific configured server
|
|
94
|
+
"""
|
|
95
|
+
@abstractmethod
|
|
96
|
+
def create_server(self: JServer) -> T { }
|
|
97
|
+
|
|
98
|
+
"""Run the server on the specified host and port."""
|
|
99
|
+
@abstractmethod
|
|
100
|
+
def run_server(self: JServer, host: str = 'localhost', port: int = 8000) -> None { }
|
|
101
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Memory Hierarchy Implementation for jac-scale.
|
|
3
|
+
|
|
4
|
+
This module provides scalable storage backends that replace the default
|
|
5
|
+
implementations in jaclang.runtimelib.memory:
|
|
6
|
+
- RedisBackend: Replaces LocalCacheMemory as L2 distributed cache
|
|
7
|
+
- MongoBackend: Replaces SqliteMemory as L3 persistent storage
|
|
8
|
+
|
|
9
|
+
ScaleTieredMemory extends TieredMemory by swapping in these backends.
|
|
10
|
+
Falls back to jaclang's SqliteMemory when MongoDB is unavailable.
|
|
11
|
+
"""
|
|
12
|
+
import logging;
|
|
13
|
+
import from collections.abc { Callable, Generator, Iterable }
|
|
14
|
+
import from pickle { dumps, loads }
|
|
15
|
+
import from typing { Any }
|
|
16
|
+
import from uuid { UUID }
|
|
17
|
+
import redis;
|
|
18
|
+
import from pymongo { MongoClient }
|
|
19
|
+
import from pymongo.errors { ConnectionFailure }
|
|
20
|
+
import from jaclang.pycore.archetype { Anchor, NodeAnchor, Root }
|
|
21
|
+
import from jaclang.runtimelib.memory {
|
|
22
|
+
CacheMemory,
|
|
23
|
+
PersistentMemory,
|
|
24
|
+
TieredMemory,
|
|
25
|
+
SqliteMemory
|
|
26
|
+
}
|
|
27
|
+
import from jaclang.runtimelib.utils { storage_key, to_uuid }
|
|
28
|
+
import from jac_scale.config_loader { get_scale_config }
|
|
29
|
+
|
|
30
|
+
# Load database configuration from jac.toml with env var overrides
|
|
31
|
+
glob _db_config = get_scale_config().get_database_config(),
|
|
32
|
+
logger = logging.getLogger(__name__);
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
Redis cache backend - implements CacheMemory for distributed L2 caching.
|
|
36
|
+
Replaces LocalCacheMemory when Redis is available.
|
|
37
|
+
"""
|
|
38
|
+
obj RedisBackend(CacheMemory) {
|
|
39
|
+
has redis_url: str = _db_config['redis_url'],
|
|
40
|
+
redis_client: (redis.Redis | None) = None;
|
|
41
|
+
|
|
42
|
+
def postinit -> None;
|
|
43
|
+
def is_available -> bool;
|
|
44
|
+
# CacheMemory interface (Memory methods + cache-specific)
|
|
45
|
+
def get(id: UUID) -> (Anchor | None);
|
|
46
|
+
def put(anchor: Anchor) -> None;
|
|
47
|
+
def delete(id: UUID) -> None;
|
|
48
|
+
def close -> None;
|
|
49
|
+
def has(id: UUID) -> bool;
|
|
50
|
+
def query(
|
|
51
|
+
filter: (Callable[[Anchor], bool] | None) = None
|
|
52
|
+
) -> Generator[Anchor, None, None];
|
|
53
|
+
|
|
54
|
+
def get_roots -> Generator[Root, None, None];
|
|
55
|
+
def find(
|
|
56
|
+
ids: (UUID | Iterable[UUID]),
|
|
57
|
+
filter: (Callable[[Anchor], Anchor] | None) = None
|
|
58
|
+
) -> Generator[Anchor, None, None];
|
|
59
|
+
|
|
60
|
+
def find_one(
|
|
61
|
+
ids: (UUID | Iterable[UUID]),
|
|
62
|
+
filter: (Callable[[Anchor], Anchor] | None) = None
|
|
63
|
+
) -> (Anchor | None);
|
|
64
|
+
|
|
65
|
+
def commit(anchor: (Anchor | None) = None) -> None;
|
|
66
|
+
# CacheMemory-specific
|
|
67
|
+
def exists(id: UUID) -> bool;
|
|
68
|
+
def put_if_exists(anchor: Anchor) -> bool;
|
|
69
|
+
def invalidate(id: UUID) -> None;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
MongoDB persistence backend - implements PersistentMemory for durable L3 storage.
|
|
74
|
+
Replaces SqliteMemory when MongoDB is available.
|
|
75
|
+
"""
|
|
76
|
+
obj MongoBackend(PersistentMemory) {
|
|
77
|
+
has client: (MongoClient | None) = None,
|
|
78
|
+
db_name: str = 'jac_db',
|
|
79
|
+
collection_name: str = 'anchors',
|
|
80
|
+
mongo_url: str = _db_config['mongodb_uri'];
|
|
81
|
+
|
|
82
|
+
def postinit -> None;
|
|
83
|
+
def is_available -> bool;
|
|
84
|
+
# PersistentMemory interface (Memory methods + persistence-specific)
|
|
85
|
+
def get(id: UUID) -> (Anchor | None);
|
|
86
|
+
def put(anchor: Anchor) -> None;
|
|
87
|
+
def delete(id: UUID) -> None;
|
|
88
|
+
def close -> None;
|
|
89
|
+
def has(id: UUID) -> bool;
|
|
90
|
+
def query(
|
|
91
|
+
filter: (Callable[[Anchor], bool] | None) = None
|
|
92
|
+
) -> Generator[Anchor, None, None];
|
|
93
|
+
|
|
94
|
+
def get_roots -> Generator[Root, None, None];
|
|
95
|
+
def find(
|
|
96
|
+
ids: (UUID | Iterable[UUID]),
|
|
97
|
+
filter: (Callable[[Anchor], Anchor] | None) = None
|
|
98
|
+
) -> Generator[Anchor, None, None];
|
|
99
|
+
|
|
100
|
+
def find_one(
|
|
101
|
+
ids: (UUID | Iterable[UUID]),
|
|
102
|
+
filter: (Callable[[Anchor], Anchor] | None) = None
|
|
103
|
+
) -> (Anchor | None);
|
|
104
|
+
|
|
105
|
+
def commit(anchor: (Anchor | None) = None) -> None;
|
|
106
|
+
# PersistentMemory-specific
|
|
107
|
+
def sync -> None;
|
|
108
|
+
def bulk_put(anchors: Iterable[Anchor]) -> None;
|
|
109
|
+
# Internal
|
|
110
|
+
def _load_anchor(raw: dict[(str, Any)]) -> (Anchor | None);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
Persistence backend type for ScaleTieredMemory.
|
|
115
|
+
"""
|
|
116
|
+
enum PersistenceType {
|
|
117
|
+
NONE,
|
|
118
|
+
MONGODB,
|
|
119
|
+
SQLITE
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
Scalable Tiered Memory - extends TieredMemory with distributed backends.
|
|
124
|
+
|
|
125
|
+
Swaps the default implementations:
|
|
126
|
+
- L2: RedisBackend instead of LocalCacheMemory (when Redis available)
|
|
127
|
+
- L3: MongoBackend instead of SqliteMemory (when MongoDB available)
|
|
128
|
+
|
|
129
|
+
Falls back to jaclang's SqliteMemory for L3 when MongoDB is unavailable.
|
|
130
|
+
Storage configuration comes from environment variables or jac.toml.
|
|
131
|
+
"""
|
|
132
|
+
obj ScaleTieredMemory(TieredMemory) {
|
|
133
|
+
has _cache_available: bool = False,
|
|
134
|
+
_persistence_type: PersistenceType = PersistenceType.NONE;
|
|
135
|
+
|
|
136
|
+
def init(use_cache: bool = True) -> None;
|
|
137
|
+
def close -> None;
|
|
138
|
+
}
|
jac_scale/plugin.jac
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""File covering plugin implementation."""
|
|
2
|
+
import os;
|
|
3
|
+
import pathlib;
|
|
4
|
+
import from dotenv { load_dotenv }
|
|
5
|
+
import from jaclang.cli.registry { get_registry }
|
|
6
|
+
import from jaclang.cli.command { Arg, ArgKind, CommandPriority, HookContext }
|
|
7
|
+
import from jaclang.pycore.runtime { hookimpl, plugin_manager }
|
|
8
|
+
import from jaclang.runtimelib.context { ExecutionContext }
|
|
9
|
+
import from jaclang.runtimelib.server { JacAPIServer as JacServer }
|
|
10
|
+
import from .context { JScaleExecutionContext }
|
|
11
|
+
import from .serve { JacAPIServer }
|
|
12
|
+
import from .jserver.jfast_api { JFastApiServer }
|
|
13
|
+
import from .config_loader { get_scale_config }
|
|
14
|
+
import from .factories.deployment_factory { DeploymentTargetFactory }
|
|
15
|
+
import from .factories.registry_factory { ImageRegistryFactory }
|
|
16
|
+
import from .factories.utility_factory { UtilityFactory }
|
|
17
|
+
import from .abstractions.config.app_config { AppConfig }
|
|
18
|
+
|
|
19
|
+
"""Pre-hook for jac start command to handle --scale flag."""
|
|
20
|
+
def _scale_pre_hook(context: HookContext) -> None {
|
|
21
|
+
scale = context.get_arg("scale", False);
|
|
22
|
+
if scale {
|
|
23
|
+
# Handle deployment instead of local server
|
|
24
|
+
filename = context.get_arg("filename");
|
|
25
|
+
build = context.get_arg("build", False);
|
|
26
|
+
target = context.get_arg("target", "kubernetes");
|
|
27
|
+
registry = context.get_arg("registry", "dockerhub");
|
|
28
|
+
if not os.path.exists(filename) {
|
|
29
|
+
raise FileNotFoundError(f"File not found: '{filename}'") ;
|
|
30
|
+
}
|
|
31
|
+
code_folder = os.path.dirname(filename) or '.';
|
|
32
|
+
dotenv_path = os.path.join(code_folder, '.env');
|
|
33
|
+
load_dotenv(dotenv_path);
|
|
34
|
+
code_folder = os.path.relpath(code_folder);
|
|
35
|
+
code_folder = pathlib.Path(code_folder).as_posix();
|
|
36
|
+
base_file_path = os.path.basename(filename);
|
|
37
|
+
# Get configuration
|
|
38
|
+
scale_config = get_scale_config();
|
|
39
|
+
# Create logger
|
|
40
|
+
logger = UtilityFactory.create_logger('standard');
|
|
41
|
+
# Get target-specific config
|
|
42
|
+
if target == 'kubernetes' {
|
|
43
|
+
target_config = scale_config.get_kubernetes_config();
|
|
44
|
+
} else {
|
|
45
|
+
# For future targets, get from config
|
|
46
|
+
target_config = scale_config.get_kubernetes_config(); # Default for now
|
|
47
|
+
}
|
|
48
|
+
# Create deployment target
|
|
49
|
+
deployment_target = DeploymentTargetFactory.create(
|
|
50
|
+
target, target_config, logger
|
|
51
|
+
);
|
|
52
|
+
# Handle image registry if build is requested
|
|
53
|
+
if build {
|
|
54
|
+
# Use target config for registry (it contains docker credentials)
|
|
55
|
+
image_registry = ImageRegistryFactory.create(registry, target_config);
|
|
56
|
+
deployment_target.image_registry = image_registry;
|
|
57
|
+
}
|
|
58
|
+
# Create app config
|
|
59
|
+
app_config = AppConfig(
|
|
60
|
+
code_folder=code_folder, file_name=base_file_path, build=build
|
|
61
|
+
);
|
|
62
|
+
# Deploy
|
|
63
|
+
result = deployment_target.deploy(app_config);
|
|
64
|
+
if not result.success {
|
|
65
|
+
raise RuntimeError(result.message or "Deployment failed") ;
|
|
66
|
+
}
|
|
67
|
+
if result.service_url {
|
|
68
|
+
print(f"Deployment complete! Service available at: {result.service_url}");
|
|
69
|
+
}
|
|
70
|
+
# Cancel normal start execution since we handled it
|
|
71
|
+
context.set_data("cancel_execution", True);
|
|
72
|
+
context.set_data("cancel_return_code", 0);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
"""Jac CLI."""
|
|
77
|
+
class JacCmd {
|
|
78
|
+
"""Create Jac CLI cmds."""
|
|
79
|
+
@hookimpl
|
|
80
|
+
static def create_cmd -> None {
|
|
81
|
+
"""Jac Scale functionality.""";
|
|
82
|
+
registry = get_registry();
|
|
83
|
+
|
|
84
|
+
# Extend jac start with --scale and related flags
|
|
85
|
+
registry.extend_command(
|
|
86
|
+
"start",
|
|
87
|
+
args=[
|
|
88
|
+
Arg.create(
|
|
89
|
+
"scale",
|
|
90
|
+
typ=bool,
|
|
91
|
+
default=False,
|
|
92
|
+
help="Deploy to a target platform instead of running locally",
|
|
93
|
+
short="" # Disable auto-generated -s (conflicts with --session)
|
|
94
|
+
),
|
|
95
|
+
Arg.create(
|
|
96
|
+
"build",
|
|
97
|
+
typ=bool,
|
|
98
|
+
default=False,
|
|
99
|
+
help="Build and push Docker image (with --scale)",
|
|
100
|
+
short="b"
|
|
101
|
+
),
|
|
102
|
+
Arg.create(
|
|
103
|
+
"target",
|
|
104
|
+
typ=str,
|
|
105
|
+
default="kubernetes",
|
|
106
|
+
help="Deployment target (kubernetes, aws, gcp, etc.)",
|
|
107
|
+
),
|
|
108
|
+
Arg.create(
|
|
109
|
+
"registry",
|
|
110
|
+
typ=str,
|
|
111
|
+
default="dockerhub",
|
|
112
|
+
help="Image registry (dockerhub, ecr, gcr, etc.)",
|
|
113
|
+
),
|
|
114
|
+
|
|
115
|
+
],
|
|
116
|
+
pre_hook=_scale_pre_hook,
|
|
117
|
+
source="jac-scale"
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
@registry.command(
|
|
121
|
+
name="destroy",
|
|
122
|
+
help="Remove deployment from target platform",
|
|
123
|
+
args=[
|
|
124
|
+
Arg.create(
|
|
125
|
+
"file_path", kind=ArgKind.POSITIONAL, help="Path to .jac file"
|
|
126
|
+
),
|
|
127
|
+
Arg.create(
|
|
128
|
+
"target",
|
|
129
|
+
typ=str,
|
|
130
|
+
default="kubernetes",
|
|
131
|
+
help="Deployment target (kubernetes, aws, gcp, etc.)",
|
|
132
|
+
),
|
|
133
|
+
|
|
134
|
+
],
|
|
135
|
+
examples=[("jac destroy app.jac", "Remove deployment for app"), ],
|
|
136
|
+
group="deployment",
|
|
137
|
+
priority=CommandPriority.PLUGIN,
|
|
138
|
+
source="jac-scale"
|
|
139
|
+
)
|
|
140
|
+
def destroy(file_path: str, target: str = "kubernetes") -> int {
|
|
141
|
+
if not os.path.exists(file_path) {
|
|
142
|
+
raise FileNotFoundError(f"File not found: '{file_path}'") ;
|
|
143
|
+
}
|
|
144
|
+
code_folder = os.path.dirname(file_path) or '.';
|
|
145
|
+
dotenv_path = os.path.join(code_folder, '.env');
|
|
146
|
+
load_dotenv(dotenv_path);
|
|
147
|
+
|
|
148
|
+
# Get configuration
|
|
149
|
+
scale_config = get_scale_config();
|
|
150
|
+
|
|
151
|
+
# Create logger
|
|
152
|
+
logger = UtilityFactory.create_logger('standard');
|
|
153
|
+
|
|
154
|
+
# Get target-specific config
|
|
155
|
+
if target == 'kubernetes' {
|
|
156
|
+
target_config = scale_config.get_kubernetes_config();
|
|
157
|
+
} else {
|
|
158
|
+
# For future targets, get from config
|
|
159
|
+
target_config = scale_config.get_kubernetes_config(); # Default for now
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# Create deployment target and destroy
|
|
163
|
+
deployment_target = DeploymentTargetFactory.create(
|
|
164
|
+
target, target_config, logger
|
|
165
|
+
);
|
|
166
|
+
app_name = os.getenv('APP_NAME') or target_config.get('app_name', 'jaseci');
|
|
167
|
+
deployment_target.destroy(app_name);
|
|
168
|
+
|
|
169
|
+
print(f"Successfully destroyed deployment '{app_name}' from {target}");
|
|
170
|
+
return 0;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
"""Jac Scale Plugin Implementation."""
|
|
176
|
+
class JacScalePlugin {
|
|
177
|
+
@hookimpl
|
|
178
|
+
static def create_j_context(user_root: (str | None)) -> ExecutionContext {
|
|
179
|
+
# Storage backend configured via environment (MONGODB_URI, etc.)
|
|
180
|
+
ctx = JScaleExecutionContext();
|
|
181
|
+
if user_root is not None {
|
|
182
|
+
ctx.set_user_root(user_root);
|
|
183
|
+
}
|
|
184
|
+
return ctx;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
"""Create the API server instance."""
|
|
188
|
+
@hookimpl
|
|
189
|
+
static def create_server(
|
|
190
|
+
jac_server: JacServer, host: str, port: int
|
|
191
|
+
) -> JFastApiServer {
|
|
192
|
+
return JFastApiServer([]);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
"""Provide jac-scale's enhanced JacAPIServer class."""
|
|
196
|
+
@hookimpl
|
|
197
|
+
static def get_api_server_class -> type {
|
|
198
|
+
return JacAPIServer;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# Pluggy's varnames() puts parameters with defaults into kwargnames, not argnames.
|
|
203
|
+
# But _multicall only passes argnames to hook implementations.
|
|
204
|
+
# JacRuntimeInterfaceImpl strips defaults via generate_plugin_helpers, so we must too.
|
|
205
|
+
import inspect;
|
|
206
|
+
glob func = JacScalePlugin.create_j_context,
|
|
207
|
+
sig = inspect.signature(func),
|
|
208
|
+
sig_nodef = sig.replace(
|
|
209
|
+
parameters=[
|
|
210
|
+
p.replace(default=inspect.Parameter.empty)
|
|
211
|
+
for p in sig.parameters.values()
|
|
212
|
+
]
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
with entry {
|
|
216
|
+
func.__signature__ = sig_nodef;
|
|
217
|
+
plugin_manager.register(JacScalePlugin());
|
|
218
|
+
}
|