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
@@ -7,7 +7,8 @@ class AppConfig {
7
7
  file_name: str = 'none',
8
8
  build: bool = False,
9
9
  app_name: (str | None) = None,
10
- testing: bool = False;
10
+ testing: bool = False,
11
+ experimental: bool = False;
11
12
 
12
13
  def init(
13
14
  self: AppConfig,
@@ -15,13 +16,15 @@ class AppConfig {
15
16
  file_name: str = 'none',
16
17
  build: bool = False,
17
18
  app_name: (str | None) = None,
18
- testing: bool = False
19
+ testing: bool = False,
20
+ experimental: bool = False
19
21
  ) -> None {
20
22
  self.code_folder = code_folder;
21
23
  self.file_name = file_name;
22
24
  self.build = build;
23
25
  self.app_name = app_name;
24
26
  self.testing = testing;
27
+ self.experimental = experimental;
25
28
  }
26
29
 
27
30
  def get_code_path(self: AppConfig) -> Path {
@@ -12,7 +12,7 @@ import os;
12
12
 
13
13
  """Scale-specific configuration loader.
14
14
 
15
- Provides access to jwt, sso, database, kubernetes, and server configuration
15
+ Provides access to jwt, sso, database, kubernetes, server and webhook configuration
16
16
  from the [plugins.scale] section of jac.toml.
17
17
  """
18
18
  class JacScaleConfig(PluginConfigBase) {
@@ -23,6 +23,7 @@ class JacScaleConfig(PluginConfigBase) {
23
23
  def get_database_config(self: JacScaleConfig) -> dict[str, Any];
24
24
  def get_kubernetes_config(self: JacScaleConfig) -> dict[str, Any];
25
25
  def get_server_config(self: JacScaleConfig) -> dict[str, Any];
26
+ def get_webhook_config(self: JacScaleConfig) -> dict[str, Any];
26
27
  }
27
28
 
28
29
  glob _scale_config_instance: JacScaleConfig | None = None;
jac_scale/context.jac CHANGED
@@ -1,8 +1,9 @@
1
+ import from contextvars { ContextVar }
1
2
  import from dataclasses { MISSING }
2
3
  import from typing { Any }
3
4
  import from uuid { UUID }
4
5
  import from jaclang.pycore.constant { Constants as Con }
5
- import from jaclang.runtimelib.context { ExecutionContext }
6
+ import from jaclang.runtimelib.context { ExecutionContext, CallState }
6
7
 
7
8
  """Jac Scale Execution Context with custom memory backend.
8
9
 
@@ -0,0 +1,75 @@
1
+ """Factory for creating storage instances."""
2
+ import from typing { Any }
3
+ import os;
4
+ import from jaclang.runtimelib.storage { Storage }
5
+ import from jaclang.project.config { get_config }
6
+
7
+ enum StorageType {
8
+ LOCAL = "local"
9
+ # Future: S3, GCS, AZURE - add when implemented
10
+ }
11
+
12
+ """Factory for creating storage instances.
13
+
14
+ Configuration priority: jac.toml > environment variable > default
15
+
16
+ In jac.toml:
17
+ [storage]
18
+ type = "local" # or "s3", "gcs", "azure"
19
+ base_path = "/data/storage"
20
+ create_dirs = true
21
+
22
+ Environment variables:
23
+ JAC_STORAGE_TYPE: Storage type (local, s3, gcs, azure)
24
+ JAC_STORAGE_PATH: Base directory for local storage
25
+ JAC_STORAGE_CREATE_DIRS: Whether to auto-create directories
26
+ """
27
+ class StorageFactory {
28
+ static def create(
29
+ storage_type: (StorageType | str), config: (dict[str, Any] | None) = None
30
+ ) -> Storage {
31
+ # Convert string to enum if needed
32
+ if isinstance(storage_type, str) {
33
+ storage_type = StorageType(storage_type);
34
+ }
35
+
36
+ if storage_type == StorageType.LOCAL {
37
+ import from jaclang.runtimelib.storage { LocalStorage }
38
+ cfg = config or {};
39
+ return LocalStorage(
40
+ base_path=cfg.get("base_path", "./storage"),
41
+ create_dirs=cfg.get("create_dirs", True)
42
+ );
43
+ }
44
+ raise ValueError(f"Unsupported storage type: {storage_type}") ;
45
+ }
46
+
47
+ static def get_default(
48
+ base_path: str = "./storage", create_dirs: bool = True
49
+ ) -> Storage {
50
+ # Get config from jac.toml if available
51
+ jac_config = get_config();
52
+
53
+ # Config priority: jac.toml > env var > parameter
54
+ if jac_config and jac_config.storage {
55
+ storage_type = StorageType(jac_config.storage.storage_type);
56
+ base_path = jac_config.storage.base_path;
57
+ create_dirs = jac_config.storage.create_dirs;
58
+ } elif os.environ.get("JAC_STORAGE_TYPE") or os.environ.get("JAC_STORAGE_PATH") {
59
+ storage_type_str = os.environ.get(
60
+ "JAC_STORAGE_TYPE", StorageType.LOCAL.value
61
+ );
62
+ storage_type = StorageType(storage_type_str);
63
+ base_path = os.environ.get("JAC_STORAGE_PATH", base_path);
64
+ create_dirs_str = os.environ.get(
65
+ "JAC_STORAGE_CREATE_DIRS", str(create_dirs)
66
+ );
67
+ create_dirs = create_dirs_str.lower() == "true";
68
+ } else {
69
+ storage_type = StorageType.LOCAL;
70
+ }
71
+
72
+ storage_config = {"base_path": base_path, "create_dirs": create_dirs};
73
+ return StorageFactory.create(storage_type, storage_config);
74
+ }
75
+ }
@@ -0,0 +1,85 @@
1
+ """Google SSO Provider implementation for jac-scale.
2
+
3
+ This module implements the SSOProvider interface for Google OAuth authentication.
4
+ """
5
+
6
+ import from fastapi { Request, Response }
7
+ import from fastapi_sso.sso.google { GoogleSSO }
8
+ import from jac_scale.sso_provider { SSOProvider, SSOUserInfo }
9
+
10
+ """Google OAuth SSO Provider.
11
+
12
+ This class wraps the fastapi_sso GoogleSSO implementation to conform to
13
+ our SSOProvider interface, enabling consistent handling across all SSO vendors.
14
+ """
15
+ obj GoogleSSOProvider(SSOProvider) {
16
+ has client_id: str,
17
+ client_secret: str,
18
+ redirect_uri: str,
19
+ allow_insecure_http: bool = True,
20
+ _google_sso: (GoogleSSO | None) = None;
21
+
22
+ """Initialize the Google SSO provider."""
23
+ def postinit -> None {
24
+ self._google_sso = GoogleSSO(
25
+ client_id=self.client_id,
26
+ client_secret=self.client_secret,
27
+ redirect_uri=self.redirect_uri,
28
+ allow_insecure_http=self.allow_insecure_http
29
+ );
30
+ }
31
+
32
+ """Initiate Google OAuth authentication flow.
33
+
34
+ Args:
35
+ operation: The operation type ('login' or 'register')
36
+
37
+ Returns:
38
+ RedirectResponse to Google's OAuth authorization page
39
+ """
40
+ async def initiate_auth(operation: str) -> Response {
41
+ if not self._google_sso {
42
+ raise RuntimeError("GoogleSSO not initialized") ;
43
+ }
44
+
45
+ with self._google_sso {
46
+ return await self._google_sso.get_login_redirect();
47
+ }
48
+ }
49
+
50
+ """Handle the OAuth callback from Google.
51
+
52
+ Args:
53
+ request: The FastAPI request object containing OAuth callback data
54
+
55
+ Returns:
56
+ SSOUserInfo: Standardized user information from Google
57
+
58
+ Raises:
59
+ Exception: If authentication fails or user info cannot be retrieved
60
+ """
61
+ async def handle_callback(request: Request) -> SSOUserInfo {
62
+ if not self._google_sso {
63
+ raise RuntimeError("GoogleSSO not initialized") ;
64
+ }
65
+
66
+ with self._google_sso {
67
+ user_info = await self._google_sso.verify_and_process(request);
68
+ return SSOUserInfo(
69
+ email=user_info.email,
70
+ external_id=user_info.id,
71
+ platform=self.get_platform_name(),
72
+ display_name=user_info?.display_name
73
+ );
74
+ }
75
+ }
76
+
77
+ """Get the platform identifier.
78
+
79
+ Returns:
80
+ str: 'google'
81
+ """
82
+ def get_platform_name -> str {
83
+ return "google";
84
+ }
85
+ }
@@ -18,7 +18,11 @@ impl JacScaleConfig.get_plugin_name(self: JacScaleConfig) -> str {
18
18
  """Get default configuration structure for scale."""
19
19
  impl JacScaleConfig.get_default_config(self: JacScaleConfig) -> dict[str, Any] {
20
20
  return {
21
- 'jwt': {'secret': 'supersecretkey', 'algorithm': 'HS256', 'exp_delta_days': 7},
21
+ 'jwt': {
22
+ 'secret': 'supersecretkey_for_testing_only!',
23
+ 'algorithm': 'HS256',
24
+ 'exp_delta_days': 7
25
+ },
22
26
  'sso': {
23
27
  'host': 'http://localhost:8000/sso',
24
28
  'google': {'client_id': '', 'client_secret': ''}
@@ -50,7 +54,13 @@ impl JacScaleConfig.get_default_config(self: JacScaleConfig) -> dict[str, Any] {
50
54
  'liveness_period': 20,
51
55
  'liveness_failure_threshold': 80
52
56
  },
53
- 'server': {'port': 8000, 'host': '0.0.0.0'}
57
+ 'server': {'port': 8000, 'host': '0.0.0.0'},
58
+ 'webhook': {
59
+ 'secret': 'webhook-secret-key',
60
+ 'signature_header': 'X-Webhook-Signature',
61
+ 'verify_signature': True,
62
+ 'api_key_expiry_days': 365
63
+ }
54
64
  };
55
65
  }
56
66
 
@@ -59,7 +69,7 @@ impl JacScaleConfig.get_jwt_config(self: JacScaleConfig) -> dict[str, Any] {
59
69
  config = self.load();
60
70
  jwt_config = config.get('jwt', {});
61
71
  return {
62
- 'secret': jwt_config.get('secret', 'supersecretkey'),
72
+ 'secret': jwt_config.get('secret', 'supersecretkey_for_testing_only!'),
63
73
  'algorithm': jwt_config.get('algorithm', 'HS256'),
64
74
  'exp_delta_days': int(jwt_config.get('exp_delta_days', 7))
65
75
  };
@@ -115,6 +125,21 @@ impl JacScaleConfig.get_server_config(self: JacScaleConfig) -> dict[str, Any] {
115
125
  };
116
126
  }
117
127
 
128
+ """Get webhook configuration from jac.toml."""
129
+ impl JacScaleConfig.get_webhook_config(self: JacScaleConfig) -> dict[str, Any] {
130
+ config = self.load();
131
+ webhook_config = config.get('webhook', {});
132
+ return {
133
+ 'secret': os.environ.get('WEBHOOK_SECRET')
134
+ or webhook_config.get('secret', 'webhook-secret-key'),
135
+ 'signature_header': webhook_config.get(
136
+ 'signature_header', 'X-Webhook-Signature'
137
+ ),
138
+ 'verify_signature': webhook_config.get('verify_signature', True),
139
+ 'api_key_expiry_days': int(webhook_config.get('api_key_expiry_days', 365))
140
+ };
141
+ }
142
+
118
143
  """Get or create the global JacScaleConfig instance."""
119
144
  impl get_scale_config(project_dir: Path | None = None) -> JacScaleConfig {
120
145
  global _scale_config_instance;
@@ -21,4 +21,5 @@ impl JScaleExecutionContext.init(self: JScaleExecutionContext) -> None {
21
21
  # Default user_root and entry_node to system_root
22
22
  self.user_root = self.system_root;
23
23
  self.entry_node = self.system_root;
24
+ self.call_state: ContextVar[CallState] = ContextVar('call_state');
24
25
  }