jac-scale 0.1.1__py3-none-any.whl → 0.1.3__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.
@@ -174,10 +174,7 @@ class KubernetesTarget(DeploymentTarget) {
174
174
  return service;
175
175
  }
176
176
 
177
- """Build the bash command for setting up Jaseci runtime environment.
178
-
179
- This replaces the hard-coded command with a configurable version.
180
- """
177
+ """Build the bash command for setting up Jaseci runtime environment."""
181
178
  def _build_runtime_setup_command(
182
179
  self: KubernetesTarget, app_config: AppConfig
183
180
  ) -> list[str] {
@@ -188,36 +185,61 @@ class KubernetesTarget(DeploymentTarget) {
188
185
  commands.append('export DEBIAN_FRONTEND=noninteractive');
189
186
  commands.append('apt-get update');
190
187
 
191
- # Install base packages
192
- base_packages = ['git', 'npm', 'nodejs'];
188
+ # Install base packages (curl and unzip needed for Bun installation)
189
+ base_packages = ['git', 'curl', 'unzip'];
193
190
  if config.additional_packages {
194
191
  base_packages.extend(config.additional_packages);
195
192
  }
196
193
  commands.append(f"apt-get install -y {' '.join(base_packages)}");
197
194
 
198
- # Clone and setup Jaseci if enabled
199
- if config.install_jaseci {
195
+ # Install Bun (required for jac-client frontend builds)
196
+ commands.append('curl -fsSL https://bun.sh/install | bash');
197
+ commands.append('export BUN_INSTALL="$HOME/.bun"');
198
+ commands.append('export PATH="$BUN_INSTALL/bin:$PATH"');
199
+
200
+ if app_config.experimental {
200
201
  commands.append('rm -rf jaseci');
201
- # Clone repository
202
202
  clone_cmd = f"git clone --branch {config.jaseci_branch} --single-branch {config.jaseci_repo_url}";
203
203
  commands.append(clone_cmd);
204
204
  commands.append('cd ./jaseci');
205
- # Checkout specific commit if provided
206
205
  if config.jaseci_commit {
207
206
  commands.append(f"git checkout {config.jaseci_commit}");
208
207
  }
209
- # Update submodules
210
208
  commands.append('git submodule update --init --recursive');
211
- # Setup virtual environment
212
- commands.append('python -m venv venv');
213
- commands.append('source venv/bin/activate');
214
- # Install Jaseci components
215
209
  commands.append('pip install pluggy');
216
210
  commands.append('pip install -e ./jac');
217
211
  commands.append('pip install -e ./jac-scale');
218
212
  commands.append('pip install -e ./jac-client');
219
213
  commands.append('pip install -e ./jac-byllm');
220
214
  commands.append('cd ..');
215
+ } else {
216
+ packages = config.plugin_versions or {};
217
+ jaclang_v = packages.get('jaclang', 'latest');
218
+ scale_v = packages.get('jac_scale', 'latest');
219
+ client_v = packages.get('jac_client', 'latest');
220
+ byllm_v = packages.get('jac_byllm', 'latest');
221
+ if jaclang_v == 'latest' {
222
+ commands.append('pip install jaclang');
223
+ } else {
224
+ commands.append(f'pip install jaclang=={jaclang_v}');
225
+ }
226
+ if scale_v == 'latest' {
227
+ commands.append('pip install jac-scale');
228
+ } else {
229
+ commands.append(f'pip install jac-scale=={scale_v}');
230
+ }
231
+ if client_v == 'latest' {
232
+ commands.append('pip install jac-client');
233
+ } else {
234
+ commands.append(f'pip install jac-client=={client_v}');
235
+ }
236
+ if byllm_v and byllm_v != 'none' {
237
+ if byllm_v == 'latest' {
238
+ commands.append('pip install byllm');
239
+ } else {
240
+ commands.append(f'pip install byllm=={byllm_v}');
241
+ }
242
+ }
221
243
  }
222
244
 
223
245
  # Change to app directory (project is already copied there via volume mount)
@@ -641,6 +663,135 @@ class KubernetesTarget(DeploymentTarget) {
641
663
  );
642
664
  }
643
665
 
666
+ """Wait for resources to be completely deleted."""
667
+ def _wait_for_deletion(
668
+ self: KubernetesTarget,
669
+ app_name: str,
670
+ namespace: str,
671
+ apps_v1: Any,
672
+ core_v1: Any,
673
+ max_wait: int = 60,
674
+ poll_interval: float = 1.0
675
+ ) -> None {
676
+ elapsed = 0.0;
677
+ while elapsed < max_wait {
678
+ resources_exist = False;
679
+
680
+ # Check deployment
681
+ try {
682
+ apps_v1.read_namespaced_deployment(name=app_name, namespace=namespace);
683
+ resources_exist = True;
684
+ } except ApiException as e {
685
+ if e.status != 404 {
686
+ raise ;
687
+ }
688
+ }
689
+
690
+ # Check service
691
+ try {
692
+ core_v1.read_namespaced_service(
693
+ name=f"{app_name}-service", namespace=namespace
694
+ );
695
+ resources_exist = True;
696
+ } except ApiException as e {
697
+ if e.status != 404 {
698
+ raise ;
699
+ }
700
+ }
701
+
702
+ # Check MongoDB resources if enabled
703
+ if self.k8s_config.mongodb_enabled {
704
+ mongodb_name = f"{app_name}-mongodb";
705
+ try {
706
+ apps_v1.read_namespaced_stateful_set(
707
+ name=mongodb_name, namespace=namespace
708
+ );
709
+ resources_exist = True;
710
+ } except ApiException as e {
711
+ if e.status != 404 {
712
+ raise ;
713
+ }
714
+ }
715
+ try {
716
+ core_v1.read_namespaced_service(
717
+ name=f"{mongodb_name}-service", namespace=namespace
718
+ );
719
+ resources_exist = True;
720
+ } except ApiException as e {
721
+ if e.status != 404 {
722
+ raise ;
723
+ }
724
+ }
725
+ }
726
+
727
+ # Check Redis resources if enabled
728
+ if self.k8s_config.redis_enabled {
729
+ redis_name = f"{app_name}-redis";
730
+ try {
731
+ apps_v1.read_namespaced_deployment(
732
+ name=redis_name, namespace=namespace
733
+ );
734
+ resources_exist = True;
735
+ } except ApiException as e {
736
+ if e.status != 404 {
737
+ raise ;
738
+ }
739
+ }
740
+ try {
741
+ core_v1.read_namespaced_service(
742
+ name=f"{redis_name}-service", namespace=namespace
743
+ );
744
+ resources_exist = True;
745
+ } except ApiException as e {
746
+ if e.status != 404 {
747
+ raise ;
748
+ }
749
+ }
750
+ }
751
+
752
+ # Check PVCs
753
+ try {
754
+ pvcs = core_v1.list_namespaced_persistent_volume_claim(namespace);
755
+ for pvc in pvcs.items {
756
+ if pvc.metadata.name.startswith(app_name) {
757
+ resources_exist = True;
758
+ break;
759
+ }
760
+ }
761
+ } except Exception { }
762
+
763
+ # Check code sync pod
764
+ try {
765
+ core_v1.read_namespaced_pod(
766
+ name=f"{app_name}-code-sync", namespace=namespace
767
+ );
768
+ resources_exist = True;
769
+ } except ApiException as e {
770
+ if e.status != 404 {
771
+ raise ;
772
+ }
773
+ }
774
+
775
+ # If no resources exist, deletion is complete
776
+ if not resources_exist {
777
+ if self.logger {
778
+ self.logger.info(
779
+ f"All resources for '{app_name}' have been deleted"
780
+ );
781
+ }
782
+ return;
783
+ }
784
+ time.sleep(poll_interval);
785
+ elapsed = elapsed + poll_interval;
786
+ }
787
+
788
+ if self.logger {
789
+ self.logger.warn(
790
+ f"Timeout waiting for resources to be deleted after {max_wait} seconds"
791
+ );
792
+ }
793
+ }
794
+
644
795
  """Destroy all enabled databases."""
645
796
  def _destroy_databases(
646
797
  self: KubernetesTarget,
@@ -727,6 +878,14 @@ class KubernetesTarget(DeploymentTarget) {
727
878
  }
728
879
  }
729
880
 
881
+ # Wait for all resources to be completely deleted
882
+ if self.logger {
883
+ self.logger.info(
884
+ f"Waiting for all resources to be deleted for '{app_name}'..."
885
+ );
886
+ }
887
+ self._wait_for_deletion(app_name, namespace, apps_v1, core_v1);
888
+
730
889
  if self.logger {
731
890
  self.logger.info(f"Application '{app_name}' destroyed successfully");
732
891
  }
@@ -0,0 +1,32 @@
1
+ """Button component for the Jac client application."""
2
+
3
+ def:pub Button(label: str, onClick: any, variant: str = "primary", disabled: bool = False) -> any {
4
+ base_styles = {
5
+ "padding": "0.75rem 1.5rem",
6
+ "fontSize": "1rem",
7
+ "fontWeight": "600",
8
+ "borderRadius": "0.5rem",
9
+ "border": "none",
10
+ "cursor": "not-allowed" if disabled else "pointer",
11
+ "transition": "all 0.2s ease"
12
+ };
13
+
14
+ variant_styles = {
15
+ "primary": {
16
+ "backgroundColor": "#9ca3af" if disabled else "#3b82f6",
17
+ "color": "#ffffff"
18
+ },
19
+ "secondary": {
20
+ "backgroundColor": "#e5e7eb" if disabled else "#6b7280",
21
+ "color": "#ffffff"
22
+ }
23
+ };
24
+
25
+ return <button
26
+ style={{**base_styles, **variant_styles[variant]}}
27
+ onClick={onClick}
28
+ disabled={disabled}
29
+ >
30
+ {label}
31
+ </button>;
32
+ }
@@ -0,0 +1,147 @@
1
+ """jac-scale features test fixture.
2
+
3
+ Demonstrates jac-scale specific features that require FastAPI:
4
+ - Storage abstraction with file uploads
5
+ - UploadFile handling
6
+ """
7
+
8
+ import from fastapi { UploadFile }
9
+ import from uuid { uuid4 }
10
+
11
+ # Initialize storage with custom base path
12
+ glob storage = store(base_path="./uploads");
13
+
14
+ # ============================================================================
15
+ # Storage API Walkers
16
+ # ============================================================================
17
+
18
+ """Upload a file to storage."""
19
+ walker:pub upload_file {
20
+ has file: UploadFile;
21
+ has folder: str = "documents";
22
+
23
+ can process with `root entry {
24
+ # Generate unique filename
25
+ ext = self.file.filename.rsplit(".", 1)[-1] if "." in self.file.filename else "";
26
+ unique_name = f"{uuid4()}.{ext}" if ext else str(uuid4());
27
+ path = f"{self.folder}/{unique_name}";
28
+
29
+ # Upload file to storage
30
+ storage.upload(self.file.file, path);
31
+
32
+ # Get metadata
33
+ metadata = storage.get_metadata(path);
34
+
35
+ report {
36
+ "success": True,
37
+ "original_filename": self.file.filename,
38
+ "storage_path": path,
39
+ "size": metadata["size"],
40
+ "content_type": self.file.content_type
41
+ };
42
+ }
43
+ }
44
+
45
+ """List files in storage."""
46
+ walker:pub list_files {
47
+ has folder: str = "";
48
+ has recursive: bool = False;
49
+
50
+ can process with `root entry {
51
+ files = [];
52
+ for path in storage.list_files(self.folder, self.recursive) {
53
+ metadata = storage.get_metadata(path);
54
+ files.append({
55
+ "path": path,
56
+ "size": metadata["size"],
57
+ "modified": str(metadata["modified"])
58
+ });
59
+ }
60
+ report {"files": files, "count": len(files)};
61
+ }
62
+ }
63
+
64
+ """Delete a file from storage."""
65
+ walker:pub delete_file {
66
+ has path: str;
67
+
68
+ can process with `root entry {
69
+ if not storage.exists(self.path) {
70
+ report {"success": False, "error": "File not found"};
71
+ return;
72
+ }
73
+ deleted = storage.delete(self.path);
74
+ report {"success": deleted, "path": self.path};
75
+ }
76
+ }
77
+
78
+ """Download a file from storage."""
79
+ walker:pub download_file {
80
+ has path: str;
81
+
82
+ can process with `root entry {
83
+ if not storage.exists(self.path) {
84
+ report {"success": False, "error": "File not found"};
85
+ return;
86
+ }
87
+ content = storage.download(self.path);
88
+ metadata = storage.get_metadata(self.path);
89
+ report {
90
+ "success": True,
91
+ "path": self.path,
92
+ "size": metadata["size"],
93
+ "content_length": len(content) if content else 0
94
+ };
95
+ }
96
+ }
97
+
98
+ """Check if a file exists in storage."""
99
+ walker:pub file_exists {
100
+ has path: str;
101
+
102
+ can process with `root entry {
103
+ exists = storage.exists(self.path);
104
+ report {"path": self.path, "exists": exists};
105
+ }
106
+ }
107
+
108
+ """Copy a file within storage."""
109
+ walker:pub copy_file {
110
+ has source: str;
111
+ has destination: str;
112
+
113
+ can process with `root entry {
114
+ if not storage.exists(self.source) {
115
+ report {"success": False, "error": "Source file not found"};
116
+ return;
117
+ }
118
+ result = storage.copy(self.source, self.destination);
119
+ report {"success": result, "source": self.source, "destination": self.destination};
120
+ }
121
+ }
122
+
123
+ """Move a file within storage."""
124
+ walker:pub move_file {
125
+ has source: str;
126
+ has destination: str;
127
+
128
+ can process with `root entry {
129
+ if not storage.exists(self.source) {
130
+ report {"success": False, "error": "Source file not found"};
131
+ return;
132
+ }
133
+ result = storage.move(self.source, self.destination);
134
+ report {"success": result, "source": self.source, "destination": self.destination};
135
+ }
136
+ }
137
+
138
+ # ============================================================================
139
+ # Health Check
140
+ # ============================================================================
141
+
142
+ """Simple health check walker."""
143
+ walker:pub health {
144
+ can check with `root entry {
145
+ report {"status": "ok", "features": ["storage", "file_upload"]};
146
+ }
147
+ }
@@ -89,6 +89,35 @@ walker : pub PublicInfo {
89
89
  }
90
90
  }
91
91
 
92
+ # Streaming Walker Test
93
+ walker : pub WalkerStream {
94
+ has count: int = 3;
95
+
96
+ can stream_reports with `root entry {
97
+ def stream -> str {
98
+ import time;
99
+ for i in range(self.count) {
100
+ time.sleep(0.5);
101
+ yield f"Report {i}";
102
+ }
103
+ }
104
+ report stream();
105
+ }
106
+ }
107
+
108
+ import from typing { Generator }
109
+ # Streaming function test
110
+ def : pub FunctionStream(count: int = 3) -> Generator {
111
+ import time;
112
+ def stream -> Generator {
113
+ for i in range(count) {
114
+ time.sleep(0.5);
115
+ yield f"Func {i}";
116
+ }
117
+ }
118
+ report stream();
119
+ }
120
+
92
121
  # Async Walker Test
93
122
  import asyncio;
94
123
 
@@ -0,0 +1,37 @@
1
+
2
+ """Test API fixture for restspec decorator tests."""
3
+
4
+ import from jaclang.runtimelib.builtin { restspec }
5
+ import from http { HTTPMethod }
6
+
7
+ # ============================================================================
8
+ # RestSpec Decorator Tests
9
+ # ============================================================================
10
+
11
+ """Walker with GET method via restspec decorator."""
12
+ @restspec(method=HTTPMethod.GET)
13
+ walker : pub GetWalker {
14
+ can get_data with `root entry {
15
+ report {"message": "GetWalker executed", "method": "GET"};
16
+ }
17
+ }
18
+
19
+ """Walker with custom path."""
20
+ @restspec(method=HTTPMethod.GET, path="/custom/walker")
21
+ walker : pub CustomPathWalker {
22
+ can get_data with `root entry {
23
+ report {"message": "CustomPathWalker executed", "path": "/custom/walker"};
24
+ }
25
+ }
26
+
27
+ """Function with GET method via restspec decorator."""
28
+ @restspec(method=HTTPMethod.GET)
29
+ def get_func() -> dict {
30
+ return {"message": "get_func executed", "method": "GET"};
31
+ }
32
+
33
+ """Function with custom path."""
34
+ @restspec(method=HTTPMethod.GET, path="/custom/func")
35
+ def custom_path_func() -> dict {
36
+ return {"message": "custom_path_func executed", "path": "/custom/func"};
37
+ }
@@ -97,10 +97,12 @@ def test_deploy_all_in_one():
97
97
  )
98
98
 
99
99
  # Create app config
100
+ # Use experimental=True to install from repo (PyPI packages may not be available)
100
101
  app_config = AppConfig(
101
102
  code_folder=todo_app_path,
102
103
  file_name="main.jac",
103
104
  build=False,
105
+ experimental=True,
104
106
  )
105
107
 
106
108
  # Deploy using new architecture
@@ -172,7 +174,6 @@ def test_deploy_all_in_one():
172
174
 
173
175
  # Cleanup using new architecture
174
176
  deployment_target.destroy(app_name)
175
- time.sleep(60) # Wait for deletion to propagate
176
177
 
177
178
  # Verify cleanup - resources should no longer exist
178
179
  try: