jac-scale 0.1.1__py3-none-any.whl → 0.1.4__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.
Files changed (36) hide show
  1. jac_scale/abstractions/config/app_config.jac +5 -2
  2. jac_scale/config_loader.jac +2 -1
  3. jac_scale/context.jac +2 -1
  4. jac_scale/factories/storage_factory.jac +75 -0
  5. jac_scale/google_sso_provider.jac +85 -0
  6. jac_scale/impl/config_loader.impl.jac +28 -3
  7. jac_scale/impl/context.impl.jac +1 -0
  8. jac_scale/impl/serve.impl.jac +749 -266
  9. jac_scale/impl/user_manager.impl.jac +349 -0
  10. jac_scale/impl/webhook.impl.jac +212 -0
  11. jac_scale/jserver/impl/jfast_api.impl.jac +4 -0
  12. jac_scale/memory_hierarchy.jac +3 -1
  13. jac_scale/plugin.jac +46 -3
  14. jac_scale/plugin_config.jac +28 -1
  15. jac_scale/serve.jac +33 -16
  16. jac_scale/sso_provider.jac +72 -0
  17. jac_scale/targets/kubernetes/kubernetes_config.jac +9 -15
  18. jac_scale/targets/kubernetes/kubernetes_target.jac +174 -15
  19. jac_scale/tests/fixtures/scale-feats/components/Button.cl.jac +32 -0
  20. jac_scale/tests/fixtures/scale-feats/main.jac +147 -0
  21. jac_scale/tests/fixtures/test_api.jac +89 -0
  22. jac_scale/tests/fixtures/test_restspec.jac +88 -0
  23. jac_scale/tests/test_deploy_k8s.py +2 -1
  24. jac_scale/tests/test_examples.py +180 -5
  25. jac_scale/tests/test_hooks.py +39 -0
  26. jac_scale/tests/test_restspec.py +289 -0
  27. jac_scale/tests/test_serve.py +411 -4
  28. jac_scale/tests/test_sso.py +273 -284
  29. jac_scale/tests/test_storage.py +274 -0
  30. jac_scale/user_manager.jac +49 -0
  31. jac_scale/webhook.jac +93 -0
  32. {jac_scale-0.1.1.dist-info → jac_scale-0.1.4.dist-info}/METADATA +11 -4
  33. {jac_scale-0.1.1.dist-info → jac_scale-0.1.4.dist-info}/RECORD +36 -23
  34. {jac_scale-0.1.1.dist-info → jac_scale-0.1.4.dist-info}/WHEEL +1 -1
  35. {jac_scale-0.1.1.dist-info → jac_scale-0.1.4.dist-info}/entry_points.txt +0 -0
  36. {jac_scale-0.1.1.dist-info → jac_scale-0.1.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,274 @@
1
+ """Tests for the storage abstraction."""
2
+
3
+ import io
4
+ import os
5
+ import shutil
6
+ import tempfile
7
+ from collections.abc import Generator
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+
12
+ try:
13
+ from jaclang.runtimelib.storage import LocalStorage # type: ignore[attr-defined]
14
+ except ImportError as e:
15
+ pytest.skip(f"Jac modules not compiled: {e}", allow_module_level=True)
16
+
17
+
18
+ @pytest.fixture
19
+ def temp_storage_dir() -> Generator[str, None, None]:
20
+ """Create a temporary directory for storage tests."""
21
+ temp_dir = tempfile.mkdtemp()
22
+ yield temp_dir
23
+ if os.path.exists(temp_dir):
24
+ shutil.rmtree(temp_dir)
25
+
26
+
27
+ @pytest.fixture
28
+ def local_storage(temp_storage_dir: str) -> Generator[LocalStorage, None, None]:
29
+ """Create a LocalStorage instance with temp directory."""
30
+ storage = LocalStorage(base_path=temp_storage_dir)
31
+ yield storage
32
+
33
+
34
+ class TestLocalStorage:
35
+ """Tests for LocalStorage implementation."""
36
+
37
+ def test_upload_from_file_path(
38
+ self, local_storage: LocalStorage, temp_storage_dir: str
39
+ ) -> None:
40
+ """Test uploading a file from a file path."""
41
+ source_file = Path(temp_storage_dir) / "source.txt"
42
+ source_file.write_text("Hello, World!")
43
+
44
+ result = local_storage.upload(str(source_file), "uploaded/file.txt")
45
+
46
+ assert local_storage.exists("uploaded/file.txt")
47
+ assert Path(result).exists()
48
+
49
+ def test_upload_from_file_object(self, local_storage: LocalStorage) -> None:
50
+ """Test uploading from a file-like object."""
51
+ file_obj = io.BytesIO(b"Binary content here")
52
+
53
+ local_storage.upload(file_obj, "binary/data.bin")
54
+
55
+ assert local_storage.exists("binary/data.bin")
56
+ content = local_storage.download("binary/data.bin")
57
+ assert content == b"Binary content here"
58
+
59
+ def test_download_returns_bytes(self, local_storage: LocalStorage) -> None:
60
+ """Test download returns bytes when no destination specified."""
61
+ file_obj = io.BytesIO(b"Test content")
62
+ local_storage.upload(file_obj, "test.txt")
63
+
64
+ content = local_storage.download("test.txt")
65
+
66
+ assert content == b"Test content"
67
+
68
+ def test_download_to_file_path(
69
+ self, local_storage: LocalStorage, temp_storage_dir: str
70
+ ) -> None:
71
+ """Test download to a file path."""
72
+ file_obj = io.BytesIO(b"Download me")
73
+ local_storage.upload(file_obj, "source.txt")
74
+ dest_path = Path(temp_storage_dir) / "downloaded.txt"
75
+
76
+ local_storage.download("source.txt", str(dest_path))
77
+
78
+ assert dest_path.exists()
79
+ assert dest_path.read_bytes() == b"Download me"
80
+
81
+ def test_download_to_file_object(self, local_storage: LocalStorage) -> None:
82
+ """Test download to a file-like object."""
83
+ file_obj = io.BytesIO(b"Stream me")
84
+ local_storage.upload(file_obj, "stream.txt")
85
+ output = io.BytesIO()
86
+
87
+ local_storage.download("stream.txt", output)
88
+
89
+ output.seek(0)
90
+ assert output.read() == b"Stream me"
91
+
92
+ def test_download_nonexistent_file_raises(
93
+ self, local_storage: LocalStorage
94
+ ) -> None:
95
+ """Test that downloading a non-existent file raises FileNotFoundError."""
96
+ with pytest.raises(FileNotFoundError):
97
+ local_storage.download("nonexistent.txt")
98
+
99
+ def test_delete_existing_file(self, local_storage: LocalStorage) -> None:
100
+ """Test deleting an existing file."""
101
+ file_obj = io.BytesIO(b"Delete me")
102
+ local_storage.upload(file_obj, "to_delete.txt")
103
+ assert local_storage.exists("to_delete.txt")
104
+
105
+ result = local_storage.delete("to_delete.txt")
106
+
107
+ assert result is True
108
+ assert not local_storage.exists("to_delete.txt")
109
+
110
+ def test_delete_nonexistent_file(self, local_storage: LocalStorage) -> None:
111
+ """Test deleting a non-existent file returns False."""
112
+ result = local_storage.delete("nonexistent.txt")
113
+
114
+ assert result is False
115
+
116
+ def test_exists_returns_true_for_existing(
117
+ self, local_storage: LocalStorage
118
+ ) -> None:
119
+ """Test exists returns True for existing file."""
120
+ file_obj = io.BytesIO(b"I exist")
121
+ local_storage.upload(file_obj, "exists.txt")
122
+
123
+ assert local_storage.exists("exists.txt") is True
124
+
125
+ def test_exists_returns_false_for_nonexistent(
126
+ self, local_storage: LocalStorage
127
+ ) -> None:
128
+ """Test exists returns False for non-existent file."""
129
+ assert local_storage.exists("nonexistent.txt") is False
130
+
131
+ def test_list_files_non_recursive(self, local_storage: LocalStorage) -> None:
132
+ """Test listing files non-recursively."""
133
+ local_storage.upload(io.BytesIO(b"1"), "folder/file1.txt")
134
+ local_storage.upload(io.BytesIO(b"2"), "folder/file2.txt")
135
+ local_storage.upload(io.BytesIO(b"3"), "folder/sub/file3.txt")
136
+
137
+ files = list(local_storage.list_files("folder", recursive=False))
138
+
139
+ # Should include file1, file2, and sub directory
140
+ assert len(files) == 3
141
+
142
+ def test_list_files_recursive(self, local_storage: LocalStorage) -> None:
143
+ """Test listing files recursively."""
144
+ local_storage.upload(io.BytesIO(b"1"), "folder/file1.txt")
145
+ local_storage.upload(io.BytesIO(b"2"), "folder/file2.txt")
146
+ local_storage.upload(io.BytesIO(b"3"), "folder/sub/file3.txt")
147
+
148
+ files = list(local_storage.list_files("folder", recursive=True))
149
+
150
+ # Should only include files (not directories) recursively
151
+ assert len(files) == 3
152
+ assert any("file1.txt" in f for f in files)
153
+ assert any("file2.txt" in f for f in files)
154
+ assert any("file3.txt" in f for f in files)
155
+
156
+ def test_get_metadata(self, local_storage: LocalStorage) -> None:
157
+ """Test getting file metadata."""
158
+ content = b"Metadata test content"
159
+ local_storage.upload(io.BytesIO(content), "meta.txt")
160
+
161
+ metadata = local_storage.get_metadata("meta.txt")
162
+
163
+ assert metadata["size"] == len(content)
164
+ assert "modified" in metadata
165
+ assert "created" in metadata
166
+ assert metadata["is_dir"] is False
167
+ assert metadata["name"] == "meta.txt"
168
+
169
+ def test_get_metadata_nonexistent_raises(self, local_storage: LocalStorage) -> None:
170
+ """Test that getting metadata of non-existent file raises error."""
171
+ with pytest.raises(FileNotFoundError):
172
+ local_storage.get_metadata("nonexistent.txt")
173
+
174
+ def test_copy_file(self, local_storage: LocalStorage) -> None:
175
+ """Test copying a file."""
176
+ local_storage.upload(io.BytesIO(b"Copy me"), "original.txt")
177
+
178
+ result = local_storage.copy("original.txt", "copied.txt")
179
+
180
+ assert result is True
181
+ assert local_storage.exists("original.txt")
182
+ assert local_storage.exists("copied.txt")
183
+ assert local_storage.download("copied.txt") == b"Copy me"
184
+
185
+ def test_copy_nonexistent_returns_false(self, local_storage: LocalStorage) -> None:
186
+ """Test copying non-existent file returns False."""
187
+ result = local_storage.copy("nonexistent.txt", "dest.txt")
188
+
189
+ assert result is False
190
+
191
+ def test_move_file(self, local_storage: LocalStorage) -> None:
192
+ """Test moving a file."""
193
+ local_storage.upload(io.BytesIO(b"Move me"), "to_move.txt")
194
+
195
+ result = local_storage.move("to_move.txt", "moved.txt")
196
+
197
+ assert result is True
198
+ assert not local_storage.exists("to_move.txt")
199
+ assert local_storage.exists("moved.txt")
200
+ assert local_storage.download("moved.txt") == b"Move me"
201
+
202
+ def test_move_nonexistent_returns_false(self, local_storage: LocalStorage) -> None:
203
+ """Test moving non-existent file returns False."""
204
+ result = local_storage.move("nonexistent.txt", "dest.txt")
205
+
206
+ assert result is False
207
+
208
+ def test_creates_directories_automatically(self, temp_storage_dir: str) -> None:
209
+ """Test that directories are created when create_dirs is True."""
210
+ new_path = os.path.join(temp_storage_dir, "new", "nested", "dir")
211
+ LocalStorage(base_path=new_path, create_dirs=True)
212
+
213
+ assert os.path.exists(new_path)
214
+
215
+ def test_upload_creates_parent_directories(
216
+ self, local_storage: LocalStorage
217
+ ) -> None:
218
+ """Test that upload creates parent directories as needed."""
219
+ file_obj = io.BytesIO(b"Nested content")
220
+
221
+ local_storage.upload(file_obj, "deep/nested/folder/file.txt")
222
+
223
+ assert local_storage.exists("deep/nested/folder/file.txt")
224
+
225
+
226
+ class TestStorageIntegration:
227
+ """Integration tests for storage operations."""
228
+
229
+ def test_full_file_lifecycle(self, local_storage: LocalStorage) -> None:
230
+ """Test complete file lifecycle: upload, read, copy, move, delete."""
231
+ # Upload
232
+ content = b"Lifecycle test content"
233
+ local_storage.upload(io.BytesIO(content), "lifecycle.txt")
234
+ assert local_storage.exists("lifecycle.txt")
235
+
236
+ # Read
237
+ downloaded = local_storage.download("lifecycle.txt")
238
+ assert downloaded == content
239
+
240
+ # Copy
241
+ local_storage.copy("lifecycle.txt", "lifecycle_copy.txt")
242
+ assert local_storage.exists("lifecycle_copy.txt")
243
+
244
+ # Move
245
+ local_storage.move("lifecycle_copy.txt", "lifecycle_moved.txt")
246
+ assert not local_storage.exists("lifecycle_copy.txt")
247
+ assert local_storage.exists("lifecycle_moved.txt")
248
+
249
+ # Delete
250
+ local_storage.delete("lifecycle.txt")
251
+ local_storage.delete("lifecycle_moved.txt")
252
+ assert not local_storage.exists("lifecycle.txt")
253
+ assert not local_storage.exists("lifecycle_moved.txt")
254
+
255
+ def test_upload_large_file(self, local_storage: LocalStorage) -> None:
256
+ """Test uploading a larger file (1MB)."""
257
+ large_content = b"x" * (1024 * 1024) # 1MB
258
+ file_obj = io.BytesIO(large_content)
259
+
260
+ local_storage.upload(file_obj, "large_file.bin")
261
+
262
+ metadata = local_storage.get_metadata("large_file.bin")
263
+ assert metadata["size"] == 1024 * 1024
264
+
265
+ downloaded = local_storage.download("large_file.bin")
266
+ assert downloaded == large_content
267
+
268
+ def test_special_characters_in_filename(self, local_storage: LocalStorage) -> None:
269
+ """Test handling files with special characters in name."""
270
+ content = b"Special chars"
271
+ local_storage.upload(io.BytesIO(content), "file-with_special.chars.txt")
272
+
273
+ assert local_storage.exists("file-with_special.chars.txt")
274
+ assert local_storage.download("file-with_special.chars.txt") == content
@@ -0,0 +1,49 @@
1
+ import jwt;
2
+ import from datetime { UTC, datetime, timedelta }
3
+ import from typing { Any }
4
+ import from fastapi { Request, Response }
5
+ import from jaclang.runtimelib.server { UserManager }
6
+ import from jaclang.runtimelib.transport { TransportResponse, Meta }
7
+ import from jac_scale.serve { Platforms, Operations }
8
+ import from jac_scale.utils { generate_random_password }
9
+ import from jac_scale.config_loader { get_scale_config }
10
+ import from jac_scale.sso_provider { SSOProvider, SSOUserInfo }
11
+ import from jac_scale.google_sso_provider { GoogleSSOProvider }
12
+
13
+ # Load configuration
14
+ glob _jwt_config = get_scale_config().get_jwt_config(),
15
+ _sso_config = get_scale_config().get_sso_config(),
16
+ JWT_SECRET = _jwt_config['secret'],
17
+ JWT_ALGORITHM = _jwt_config['algorithm'],
18
+ JWT_EXP_DELTA_DAYS = _jwt_config['exp_delta_days'],
19
+ SSO_HOST = _sso_config['host'];
20
+
21
+ obj JacScaleUserManager(UserManager) {
22
+ has SUPPORTED_PLATFORMS: dict = {};
23
+
24
+ def postinit -> None;
25
+ # JWT methods
26
+ def create_jwt_token(username: str) -> str;
27
+ def validate_jwt_token(token: str) -> (str | None);
28
+ def refresh_jwt_token(token: str) -> (str | None);
29
+ # SSO methods
30
+ def get_sso(platform: str, operation: str) -> (SSOProvider | None);
31
+ async def sso_initiate(
32
+ platform: str, operation: str
33
+ ) -> (Response | TransportResponse);
34
+
35
+ async def sso_callback(
36
+ request: Request, platform: str, operation: str
37
+ ) -> TransportResponse;
38
+
39
+ # SSO Account Linking methods
40
+ def link_sso_account(
41
+ user_id: str, platform: str, external_id: str, email: str
42
+ ) -> dict[str, str];
43
+
44
+ def unlink_sso_account(user_id: str, platform: str) -> dict[str, str];
45
+ def get_sso_accounts(user_id: str) -> list[dict[str, str]];
46
+ def get_user_by_sso(platform: str, external_id: str) -> (dict[str, str] | None);
47
+ # Override validate_token to use JWT
48
+ def validate_token(token: str) -> (str | None);
49
+ }
jac_scale/webhook.jac ADDED
@@ -0,0 +1,93 @@
1
+ """Webhook support for Jac Scale.
2
+
3
+ This module provides webhook-related functionality including:
4
+ - API key generation and management
5
+ - HMAC-SHA256 signature verification for webhook security
6
+ - Webhook configuration and utilities
7
+
8
+ API keys are generated per-user and can be used to authenticate webhook calls.
9
+ The API key is wrapped in a JWT for secure storage and validation.
10
+ """
11
+ import hmac;
12
+ import hashlib;
13
+ import secrets;
14
+ import jwt;
15
+ import from datetime { UTC, datetime, timedelta }
16
+ import from typing { Any }
17
+ import from pydantic { BaseModel, Field }
18
+ import from jaclang.runtimelib.transport { TransportResponse, Meta }
19
+ import from jac_scale.config_loader { get_scale_config }
20
+
21
+ # Load webhook configuration
22
+ glob _webhook_config = get_scale_config().get_webhook_config(),
23
+ WEBHOOK_SECRET = _webhook_config['secret'],
24
+ WEBHOOK_SIGNATURE_HEADER = _webhook_config['signature_header'],
25
+ WEBHOOK_VERIFY_SIGNATURE = _webhook_config['verify_signature'],
26
+ WEBHOOK_API_KEY_EXPIRY_DAYS = _webhook_config['api_key_expiry_days'];
27
+
28
+ """Pydantic model for API key creation request."""
29
+ class CreateApiKeyRequest(BaseModel) {
30
+ has name: str = Field(..., description='A friendly name for the API key'),
31
+ expiry_days: int | None = Field(
32
+ None, description='Number of days until expiry (default from config)'
33
+ );
34
+ }
35
+
36
+ """Pydantic model for API key response."""
37
+ class ApiKeyResponse(BaseModel) {
38
+ has api_key: str = Field(..., description='The generated API key'),
39
+ api_key_id: str = Field(..., description='Unique identifier for the API key'),
40
+ name: str = Field(..., description='Friendly name for the API key'),
41
+ created_at: str = Field(..., description='ISO timestamp of creation'),
42
+ expires_at: str | None = Field(
43
+ None, description='ISO timestamp of expiry, or null if no expiry'
44
+ );
45
+ }
46
+
47
+ """Webhook utilities for signature generation and verification."""
48
+ obj WebhookUtils {
49
+ """Generate HMAC-SHA256 signature for webhook payload."""
50
+ static def generate_signature(payload: bytes, secret: str) -> str;
51
+
52
+ """Verify HMAC-SHA256 signature for webhook payload."""
53
+ static def verify_signature(payload: bytes, signature: str, secret: str) -> bool;
54
+
55
+ """Generate a new API key."""
56
+ static def generate_api_key -> str;
57
+
58
+ """Create a JWT-wrapped API key token.
59
+
60
+ The API key is embedded in a JWT for secure validation and expiry management."""
61
+ static def create_api_key_token(
62
+ api_key_id: str, username: str, name: str, expiry_days: int | None = None
63
+ ) -> str;
64
+
65
+ """Validate an API key token and extract user information."""
66
+ static def validate_api_key(api_key: str) -> dict[str, str] | None;
67
+
68
+ """Extract signature from request header value."""
69
+ static def extract_signature(header_value: str) -> str;
70
+ }
71
+
72
+ """Manager for API keys associated with users."""
73
+ obj ApiKeyManager {
74
+ has _api_keys: dict[str, dict[str, Any]] = {}, # api_key_id -> key info
75
+ _user_keys: dict[str, list[str]] = {}; # username -> list of api_key_ids
76
+
77
+ """Create a new API key for a user."""
78
+ def create_api_key(
79
+ username: str, name: str, expiry_days: int | None = None
80
+ ) -> TransportResponse;
81
+
82
+ """List all API keys for a user."""
83
+ def list_api_keys(username: str) -> TransportResponse;
84
+
85
+ """Revoke an API key."""
86
+ def revoke_api_key(username: str, api_key_id: str) -> TransportResponse;
87
+
88
+ """Validate an API key and return the associated username."""
89
+ def validate_api_key(api_key: str) -> str | None;
90
+
91
+ """Check if an API key ID exists and is not revoked."""
92
+ def is_key_active(api_key_id: str) -> bool;
93
+ }
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jac-scale
3
- Version: 0.1.1
3
+ Version: 0.1.4
4
4
  Author-email: Jason Mars <jason@mars.ninja>
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
7
- Requires-Dist: jaclang>=0.9.10
7
+ Requires-Dist: jaclang>=0.9.13
8
8
  Requires-Dist: python-dotenv<2.0.0,>=1.2.1
9
9
  Requires-Dist: docker<8.0.0,>=7.1.0
10
10
  Requires-Dist: kubernetes<35.0.0,>=34.1.0
@@ -12,7 +12,7 @@ Requires-Dist: pymongo<5.0.0,>=4.15.4
12
12
  Requires-Dist: redis<8.0.0,>=7.1.0
13
13
  Requires-Dist: fastapi<0.122.0,>=0.121.3
14
14
  Requires-Dist: uvicorn<0.39.0,>=0.38.0
15
- Requires-Dist: pyjwt
15
+ Requires-Dist: pyjwt<2.11.0,>=2.10.1
16
16
  Requires-Dist: fastapi-sso<1.0.0,>=0.18.0
17
17
  Requires-Dist: python-multipart<1.0.0,>=0.0.21
18
18
 
@@ -43,6 +43,13 @@ Requires-Dist: python-multipart<1.0.0,>=0.0.21
43
43
 
44
44
  Whether you're developing locally with `jac start` or deploying to production with `jac start --scale`, you get the same powerful features with the flexibility to choose your deployment strategy.
45
45
 
46
+ ### 4. Single Sign-On (SSO) Support
47
+
48
+ - **Google SSO**: Built-in support for Google Sign-In out of the box
49
+ - **Extensible Architecture**: Easily add other providers (GitHub, Microsoft, etc.)
50
+ - **Secure Authentication**: Integrated with JWT for secure session management
51
+ - **User Management**: Automatic account creation and linking
52
+
46
53
  ## Prerequisites
47
54
 
48
55
  - kubenetes(K8s) installed
@@ -484,7 +491,7 @@ async walker FetchData {
484
491
  | `MONGODB_URI` | URL of MongoDB database | - |
485
492
  | `REDIS_URL` | URL of Redis database | - |
486
493
  | `JWT_EXP_DELTA_DAYS` | Number of days until JWT token expires | `7` |
487
- | `JWT_SECRET` | Secret key used for JWT token signing and verification | `'supersecretkey'` |
494
+ | `JWT_SECRET` | Secret key used for JWT token signing and verification | `'supersecretkey_for_testing_only!'` |
488
495
  | `JWT_ALGORITHM` | Algorithm used for JWT token encoding/decoding | `'HS256'` |
489
496
  | `SSO_HOST` | SSO host URL | `'http://localhost:8000/sso'` |
490
497
  | `SSO_GOOGLE_CLIENT_ID` | Google OAuth client ID | - |
@@ -1,57 +1,70 @@
1
1
  jac_scale/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- jac_scale/config_loader.jac,sha256=oOOOFcFfEP89Y3A-AI4cLqrEaO6g0jJqZglBsCoKM84,1222
3
- jac_scale/context.jac,sha256=Ezq5-9q2rBGhs5TTwYrVkFJ4nJN3e_Sgv0wM4eT9Qmc,479
4
- jac_scale/memory_hierarchy.jac,sha256=GFaLQNLMqHvS5anYNWD_TcsW8iAYIMk-7gNzWcoBGpU,4469
5
- jac_scale/plugin.jac,sha256=6kx1gE6hYtcOHECAUwkLVQEceQF47EMpXU7EIeQLd4o,8047
6
- jac_scale/plugin_config.jac,sha256=yaXyP1nUSDtVrTBFGwa0_NDeVHNlbEP1zGSgioe-F5k,7230
7
- jac_scale/serve.jac,sha256=9ZLK4Xfu3DbmX50QE02EaNW8L5ekfuu2Br8rkdYrwwk,4775
2
+ jac_scale/config_loader.jac,sha256=fG1dN9AS54bzNL5XvPHNS4oMw8kknZnlAZ7ByPT0APU,1298
3
+ jac_scale/context.jac,sha256=SIqQineAlLHJV3yWtYkeTIfcnqS0Oj15n8CWVSIqwJ8,529
4
+ jac_scale/google_sso_provider.jac,sha256=UUTDgQrBHXp0eqHxiw7v_VkQyFMMza0UDM6S4T5e4OM,2473
5
+ jac_scale/memory_hierarchy.jac,sha256=GuydhwujsH71TAkxeJ6Wfutp4v6GjDj23vGMreRj4yg,4533
6
+ jac_scale/plugin.jac,sha256=E1Ou_sQ8RV35emY9-gM295Uu-Eg4w4qSEJYm3TTYsWY,9585
7
+ jac_scale/plugin_config.jac,sha256=6_cTMzjDhr-7fGh2RMM5LCfjXlfKduGyFs0S0PnHKXQ,8718
8
+ jac_scale/serve.jac,sha256=8xMOHX3X-sJFx6yENolL-7VM7Km1Mg5BPkUtMLUl9O4,5546
9
+ jac_scale/sso_provider.jac,sha256=Kky8cxm5gR2Sbg-CFrWTILKSZrV6Ju-DCEgfG4Akxb8,2092
10
+ jac_scale/user_manager.jac,sha256=UBApt_znhVEyxtfAdJ9sp_gslkHel4GGwXCBcNen_as,1932
8
11
  jac_scale/utils.jac,sha256=zpxA08_NlDDSd1oVlbknQ_UOwHWGBbhzjmT_u6UYN04,491
12
+ jac_scale/webhook.jac,sha256=lTpevPoe-CEcj0-MAd8c1fHK7jGUL3YnN4_ouMeq4Ic,3660
9
13
  jac_scale/abstractions/database_provider.jac,sha256=NZbFmcESulYsmG-27f8GY9nxVs3JflGIEb3aDDOIPu0,1454
10
14
  jac_scale/abstractions/deployment_target.jac,sha256=5rJOd25FUXZZCNmDSUkyMljcXTAVJJDmelKw8WdPZm0,2071
11
15
  jac_scale/abstractions/image_registry.jac,sha256=UCOwwuts5e1emsdDIlHJOiyW2ePA7k7D8UJdzd2kN10,1386
12
16
  jac_scale/abstractions/logger.jac,sha256=YnXn8_jCpZ0IOBkJ7bFBMqo6WXO6BaDNVBqPq0lHbjk,652
13
- jac_scale/abstractions/config/app_config.jac,sha256=XD-VtltmHaXPFdVWw6HRqLJjSTqxS4fwQFZTnbGoYSQ,798
17
+ jac_scale/abstractions/config/app_config.jac,sha256=yxXlTOFRNBq5yFMgYm1LjEzobwxP_k2EG4zXcoHf688,912
14
18
  jac_scale/abstractions/config/base_config.jac,sha256=ePT-up-63QGu2Jifi_CpPhd7_V9cPcHkInqGKDFryC4,785
15
19
  jac_scale/abstractions/models/deployment_result.jac,sha256=3Mx8TVvL74AF8LLnhMbuCyHTTu-DXesYWMvht1HoGFs,704
16
20
  jac_scale/abstractions/models/resource_status.jac,sha256=H6I9ZF92ia56bNmHR9dLMGsaFG4BafGh4sY-YwVRPu0,1031
17
21
  jac_scale/factories/database_factory.jac,sha256=-8QfF1vDUqsdDKnldmayxvB-Z6DwQeNMOlo7HgTaouQ,1736
18
22
  jac_scale/factories/deployment_factory.jac,sha256=1pzQ2HkqpKaeoycbRxdZ05McgTLpgj5Y5hmKhFTm1AY,1673
19
23
  jac_scale/factories/registry_factory.jac,sha256=qWgwEH75EtjXGj3d9NGeg7D9Yx2S_RW9udlCFvOOCqU,1210
24
+ jac_scale/factories/storage_factory.jac,sha256=FVlx-f1BOq6H1yLWXT7OLSG-ZCENvlBPNr0OwFdo-VU,2661
20
25
  jac_scale/factories/utility_factory.jac,sha256=64gMv_N_pFeq5E7z3gXrLIqeRGS_RGIF5eLT8RxNl_I,1262
21
- jac_scale/impl/config_loader.impl.jac,sha256=j8YngI4mdcBKCBtly-8vN0WYXDwzdhwZ6bmmMc9Kza4,4769
22
- jac_scale/impl/context.impl.jac,sha256=LU9R3318_eXOOgLcv2x76Cl6lxmxPlILDI7E9U3QzUU,1000
26
+ jac_scale/impl/config_loader.impl.jac,sha256=gIcOowYuoMvpilRK6vhAGf5ULYttrkh19fB5zP7KTdI,5668
27
+ jac_scale/impl/context.impl.jac,sha256=3M33iA9L2SCIMT0rQTvb8MkveNCuTEjc7rNknK4Hruk,1071
23
28
  jac_scale/impl/memory_hierarchy.main.impl.jac,sha256=Jc4Cy-5cy0TtK2uQ6rdYruxa0ZfZKSYgo3jOoXv4ER4,2339
24
29
  jac_scale/impl/memory_hierarchy.mongo.impl.jac,sha256=APh7YloOj7bWGtv6DTBm7ekX13w_fectbZiFY0btwWU,6626
25
30
  jac_scale/impl/memory_hierarchy.redis.impl.jac,sha256=x27TzeBI87vz72s7esQvFz8EPk3EfhVMNKU8sP4s2hk,4900
26
- jac_scale/impl/serve.impl.jac,sha256=zs3JgM3w7e-fSZjnICUEJ8gcli--9XkE5yHF6kQiowI,66860
31
+ jac_scale/impl/serve.impl.jac,sha256=zzWAF0PukDMdKKFmiwP-cupxrCA8YtZ_leYY-Qzcnkc,85767
32
+ jac_scale/impl/user_manager.impl.jac,sha256=7xGoLMweQuoVW-na7XbK3bctfUHaSEnT9nB7DLgSlgw,11817
33
+ jac_scale/impl/webhook.impl.jac,sha256=87IZn9nfSaNXX9RYXVJRJEz-3xzznpC6RhVBoJJA2X8,7504
27
34
  jac_scale/jserver/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
35
  jac_scale/jserver/jfast_api.jac,sha256=Ez7EZ883TS9IIACzK8QMqTgVTtNoaOenbmwNKnekBuc,5561
29
36
  jac_scale/jserver/jserver.jac,sha256=8gWh22nX9Z5rJp3D1AxOSU8lBhoX4-kBZ0uvJPPNqX8,3356
30
- jac_scale/jserver/impl/jfast_api.impl.jac,sha256=fJZDFKbFYhpYqIcauPNPd5tpRHx_Xd1DXo49-vTSXQA,26766
37
+ jac_scale/jserver/impl/jfast_api.impl.jac,sha256=bnxoReOOntQL0FHDgLSoaXtQRtCYOOEVWhq-lAXeqI0,26931
31
38
  jac_scale/jserver/impl/jserver.impl.jac,sha256=4NOzfys4WQmGt6vttTTh6By12hEJqdvOzCZyCBsUQKk,2224
32
39
  jac_scale/providers/database/kubernetes_mongo.jac,sha256=nP43b-ePR8XInhhk7gzdvJvOJMZcTZanm1VALdiCqXA,4952
33
40
  jac_scale/providers/database/kubernetes_redis.jac,sha256=qF9HkNLLgiymxiCXbmETbVVDs5Wqs1m4s3O6xFR7jRA,3778
34
41
  jac_scale/providers/registry/dockerhub.jac,sha256=7KlgQJGGUeDF7zP8-KNF8HTShB4td1946OtIdS57EHI,2282
35
- jac_scale/targets/kubernetes/kubernetes_config.jac,sha256=R0h6g9-z9_K9Z4FpO2-t6Wefc-y_aLtLb3YeqPNU30Y,9513
36
- jac_scale/targets/kubernetes/kubernetes_target.jac,sha256=Dv-uq_HyhQm8czdak2_u0BK1gJ0ohjaHNU8Pgtb1BUE,29928
42
+ jac_scale/targets/kubernetes/kubernetes_config.jac,sha256=Xk3EjKTEjptlctD2C5cTYjgw9V8i8mfsO-jKAa2IZZs,9406
43
+ jac_scale/targets/kubernetes/kubernetes_target.jac,sha256=itICKs_sA4jOs7_wrFgpj5QhupjKVXRSvkaJMBL6t9g,35523
37
44
  jac_scale/targets/kubernetes/utils/kubernetes_utils.impl.jac,sha256=CPSlFlhCE3qdzPWiPVa7KbHkL_h5Gle5ySvq7UkwR4k,15756
38
45
  jac_scale/targets/kubernetes/utils/kubernetes_utils.jac,sha256=Nr257oCyduodJbxkLJeNYphFzz9NxviFfW1Tpc5dQKk,2129
39
46
  jac_scale/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
47
  jac_scale/tests/conftest.py,sha256=uiS352_HVmMf7V6T0BZl-Kn8svkKbj3jhNr-hEKrcPw,820
41
48
  jac_scale/tests/test_abstractions.py,sha256=gb5ueNcUcxpq0U0r_nEize9v027gRR_XiZlCEooAW6U,2612
42
- jac_scale/tests/test_deploy_k8s.py,sha256=iV7q1hK7BMulV4H_Rg9VJ3NrFFoEFE2gVWyvK7Zl6HE,9104
43
- jac_scale/tests/test_examples.py,sha256=FlmVbrCJk0Jt_NTSExctSI5sNShqS5vWa0eMyek1itQ,16898
49
+ jac_scale/tests/test_deploy_k8s.py,sha256=rX9kJohqTeaRch-KSruWYF8eUnPGtJ_p6zy0sPkBXIA,9164
50
+ jac_scale/tests/test_examples.py,sha256=2xOTf0rBiT_9w92ku7k9Pfv4A7ohLZpZdpAH0VHM86E,24044
44
51
  jac_scale/tests/test_factories.py,sha256=cf1gLrBdK9q8SlHyocZqtixyPtO2dLlrgNYeX5-_RYM,5073
45
52
  jac_scale/tests/test_file_upload.py,sha256=JooF-9rgwOqtNkWRfvfHu4W5x-gzk91bHVi13J9mnNE,16595
53
+ jac_scale/tests/test_hooks.py,sha256=TzikM6dd1uKg1G2L1MtgXYzzjY6YhrbAQXZah-TO33Y,1366
46
54
  jac_scale/tests/test_k8s_utils.py,sha256=Oqocwl4m2IFgM27Lgsi96WJeHS8hnx9WDOcxq9YkqJU,4613
47
55
  jac_scale/tests/test_memory_hierarchy.py,sha256=q_DsKyPIuQymab_sEtXojDW0GOyunRxZJsNTJDrGA7s,8324
48
- jac_scale/tests/test_serve.py,sha256=VhZdU75PDMH71DEMKWkfs2KTUW6mpK9ZzpWOglCpFEk,65119
49
- jac_scale/tests/test_sso.py,sha256=hFFvjbrgtO_5557XaEwZLjrrQsc4k4S3GPTroU2bwPA,26779
50
- jac_scale/tests/fixtures/test_api.jac,sha256=JBQUXb7l7M3cHGjcY7I9e6XhQyLQY7FKiC8z1Ei7Ve8,4109
56
+ jac_scale/tests/test_restspec.py,sha256=fYNpQi7ZhV7KnSoJQ4xH1tJIQeayaeCvFS5GBLAZO1o,10853
57
+ jac_scale/tests/test_serve.py,sha256=xKPjQJLdtWaCISlP3Kirf3A4i1Fxubej_h5oLklKSY8,81244
58
+ jac_scale/tests/test_sso.py,sha256=9h2AUC9JBM74O6xfpyIcl98iXyZWcDIGdKRtlb_5uqo,28104
59
+ jac_scale/tests/test_storage.py,sha256=Y7CIbKrH3OmJQlBeV9A965-7TtxL6nuZtfkfoeqIeS4,10514
60
+ jac_scale/tests/fixtures/test_api.jac,sha256=aqfvy4wgc0qPTM9_NYvXUcpemNTSdwW0tp432sdHZI4,6680
61
+ jac_scale/tests/fixtures/test_restspec.jac,sha256=oFqACGtuoGz1xsURAaeXrQe7hkMI-W8hL5IKFDTQHKU,2511
51
62
  jac_scale/tests/fixtures/todo_app.jac,sha256=beI6AiRutmXsXxuzU9nIZTE-AuoBBP-WqbRA2ux1pN4,1216
63
+ jac_scale/tests/fixtures/scale-feats/main.jac,sha256=QN8Opodha9jLWdRGCASByIogePr1XKphgKjyE9Pp73c,4205
64
+ jac_scale/tests/fixtures/scale-feats/components/Button.cl.jac,sha256=e8tvNzW_QikFJMSTcbkziKP69NF00DqQVxcjLPkAW-w,903
52
65
  jac_scale/utilities/loggers/standard_logger.jac,sha256=6XL5ETAOBwbsFCOp0VN_7TOnqcQDmbLZVzubA-JR3vA,1376
53
- jac_scale-0.1.1.dist-info/METADATA,sha256=I-nL8nlf_Bc45KaJLHgTji6ozrF9I-xNHg3drRXM-8g,20125
54
- jac_scale-0.1.1.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
55
- jac_scale-0.1.1.dist-info/entry_points.txt,sha256=n-Wm8JEtGOqy_IY_kgIOi3-uYnuVK-iWsvKiLkxlG4E,105
56
- jac_scale-0.1.1.dist-info/top_level.txt,sha256=PpgR0R8z9qoFbSser2K20r5Is4K6TxVwguoN6LfTEKU,10
57
- jac_scale-0.1.1.dist-info/RECORD,,
66
+ jac_scale-0.1.4.dist-info/METADATA,sha256=F_sIgqYZVaPGz7cPpx-Sc-EqI0cNX43nEkFWQiUjU5w,20491
67
+ jac_scale-0.1.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
68
+ jac_scale-0.1.4.dist-info/entry_points.txt,sha256=n-Wm8JEtGOqy_IY_kgIOi3-uYnuVK-iWsvKiLkxlG4E,105
69
+ jac_scale-0.1.4.dist-info/top_level.txt,sha256=PpgR0R8z9qoFbSser2K20r5Is4K6TxVwguoN6LfTEKU,10
70
+ jac_scale-0.1.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5