jvserve 2.1.2__py3-none-any.whl → 2.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.

Potentially problematic release.


This version of jvserve might be problematic. Click here for more details.

jvserve/cli.py CHANGED
@@ -3,12 +3,16 @@
3
3
  import asyncio
4
4
  import logging
5
5
  import os
6
+ import sys
7
+ import threading
8
+ import time
6
9
  from concurrent.futures import ThreadPoolExecutor
7
10
  from contextlib import asynccontextmanager
8
11
  from pickle import load
9
12
  from typing import AsyncIterator, Optional
10
13
 
11
14
  import aiohttp
15
+ import psutil
12
16
  import pymongo
13
17
  from bson import ObjectId
14
18
  from dotenv import load_dotenv
@@ -21,7 +25,7 @@ from jac_cloud.plugin.jaseci import NodeAnchor
21
25
  from jaclang import JacMachine as Jac
22
26
  from jaclang.cli.cmdreg import cmd_registry
23
27
  from jaclang.runtimelib.machine import hookimpl
24
- from watchfiles import Change, run_process
28
+ from watchfiles import Change, watch
25
29
 
26
30
  from jvserve.lib.agent_interface import AgentInterface
27
31
  from jvserve.lib.file_interface import (
@@ -43,6 +47,10 @@ logger = logging.getLogger(__name__)
43
47
  url_proxy_collection = None
44
48
  collection_init_lock = asyncio.Lock()
45
49
 
50
+ # Global state for watcher control
51
+ WATCHER_STATE_FILE = ".jvserve_watcher_state"
52
+ watcher_enabled = True
53
+
46
54
 
47
55
  async def get_url_proxy_collection() -> pymongo.collection.Collection:
48
56
  """Thread-safe initialization of MongoDB collection"""
@@ -96,6 +104,37 @@ async def serve_proxied_file(
96
104
  raise HTTPException(status_code=502, detail=f"File fetch error: {str(e)}")
97
105
 
98
106
 
107
+ def start_file_watcher(
108
+ watchdir: str, filename: str, host: str, port: int
109
+ ) -> threading.Thread:
110
+ """Start the file watcher in a separate thread"""
111
+
112
+ def watcher_loop() -> None:
113
+ """File watcher loop that runs in a separate thread"""
114
+ global watcher_enabled
115
+
116
+ logger.info(f"Starting file watcher for directory: {watchdir}")
117
+
118
+ try:
119
+ for changes in watch(watchdir):
120
+ if watcher_enabled:
121
+ log_reload(changes)
122
+ # Kill the current server process and restart
123
+ reload_server()
124
+ else:
125
+ logger.info("Watcher disabled, ignoring changes")
126
+ time.sleep(1) # Prevent busy loop when disabled
127
+ except KeyboardInterrupt:
128
+ logger.info("File watcher stopped")
129
+ except Exception as e:
130
+ logger.error(f"File watcher error: {e}")
131
+
132
+ # Start watcher in daemon thread so it doesn't prevent program exit
133
+ watcher_thread = threading.Thread(target=watcher_loop, daemon=True)
134
+ watcher_thread.start()
135
+ return watcher_thread
136
+
137
+
99
138
  def run_jivas(filename: str, host: str = "localhost", port: int = 8000) -> None:
100
139
  """Starts JIVAS server with integrated file services"""
101
140
 
@@ -219,12 +258,32 @@ def run_jivas(filename: str, host: str = "localhost", port: int = 8000) -> None:
219
258
  raise HTTPException(status_code=500, detail="Internal server error")
220
259
 
221
260
  ctx.close()
261
+
262
+ # Start file watcher BEFORE starting the server (in development mode)
263
+ is_development = os.environ.get("JIVAS_ENVIRONMENT") == "development"
264
+ if is_development:
265
+ watchdir = os.path.join(
266
+ os.path.abspath(os.path.dirname(filename)), "actions", ""
267
+ )
268
+ logger.info("Development mode: Starting file watcher")
269
+ enable_watcher()
270
+ start_file_watcher(watchdir, filename, host, port)
271
+
222
272
  # Run the app
223
273
  JaseciFastAPI.start(host=host, port=port)
224
274
 
225
275
 
226
276
  def log_reload(changes: set[tuple[Change, str]]) -> None:
227
- """Log changes."""
277
+ """Log changes and check watcher state."""
278
+ global watcher_enabled
279
+
280
+ logger.warning(f"Watcher is: {watcher_enabled}")
281
+
282
+ # Check if watcher is disabled
283
+ if not watcher_enabled:
284
+ logger.warning("Watcher is disabled. Ignoring changes.")
285
+ return
286
+
228
287
  num_of_changes = len(changes)
229
288
  logger.warning(
230
289
  f'Detected {num_of_changes} change{"s" if num_of_changes > 1 else ""}'
@@ -245,12 +304,60 @@ class JacCmd:
245
304
  @cmd_registry.register
246
305
  def jvserve(filename: str, host: str = "localhost", port: int = 8000) -> None:
247
306
  """Launch unified JIVAS server with file services"""
248
- watchdir = os.path.join(
249
- os.path.abspath(os.path.dirname(filename)), "actions", ""
250
- )
251
- run_process(
252
- watchdir,
253
- target=run_jivas,
254
- args=(filename, host, port),
255
- callback=log_reload,
256
- )
307
+ run_jivas(filename, host, port)
308
+
309
+
310
+ def disable_watcher() -> dict:
311
+ """Disable the watcher from auto-reloading"""
312
+ if os.environ.get("JIVAS_ENVIRONMENT") == "development":
313
+ global watcher_enabled
314
+ watcher_enabled = False
315
+ with open(WATCHER_STATE_FILE, "w") as f:
316
+ f.write("disabled")
317
+ return {"message": "Watcher disabled"}
318
+ else:
319
+ return {"message": "Watcher already disabled"}
320
+
321
+
322
+ def enable_watcher() -> dict:
323
+ """Enable the watcher for auto-reloading"""
324
+ if os.environ.get("JIVAS_ENVIRONMENT") == "development":
325
+ global watcher_enabled
326
+ watcher_enabled = True
327
+ with open(WATCHER_STATE_FILE, "w") as f:
328
+ f.write("enabled")
329
+ return {"message": "Watcher enabled"}
330
+ else:
331
+ return {"message": "Watcher already enabled"}
332
+
333
+
334
+ def reload_server() -> None:
335
+ """Reload the server using the exact command that started it."""
336
+ try:
337
+ # Get the command used to start the sever
338
+ current_process = psutil.Process(os.getpid())
339
+ cmdline = current_process.cmdline()
340
+
341
+ logger.info(f"Restarting with command: {' '.join(cmdline)}")
342
+
343
+ # Replace current process with the same command
344
+ os.execvp(cmdline[0], cmdline)
345
+
346
+ except Exception as e:
347
+ logger.error(f"Failed to get process command line: {e}")
348
+ # Fallback to sys.argv
349
+ reload_server_from_argv()
350
+ finally:
351
+ is_development = os.environ.get("JIVAS_ENVIRONMENT") == "development"
352
+
353
+ if is_development:
354
+ enable_watcher()
355
+
356
+
357
+ def reload_server_from_argv() -> None:
358
+ """Reload using sys.argv (the original command line arguments)."""
359
+ logger.info("Reloading server using sys.argv...")
360
+ logger.info(f"Original command: {' '.join(sys.argv)}")
361
+
362
+ # sys.argv[0] is the script name, rest are arguments
363
+ os.execvp(sys.executable, [sys.executable] + sys.argv)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jvserve
3
- Version: 2.1.2
3
+ Version: 2.1.4
4
4
  Summary: FastAPI webserver for loading and interaction with JIVAS agents.
5
5
  Home-page: https://github.com/TrueSelph/jvserve
6
6
  Author: TrueSelph Inc.
@@ -10,6 +10,7 @@ Requires-Python: >=3.12.0
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
12
  Requires-Dist: jac-cloud
13
+ Requires-Dist: psutil>=7.0.0
13
14
  Requires-Dist: jaclang==0.8.4
14
15
  Requires-Dist: pyaml>=25.1.0
15
16
  Requires-Dist: requests>=2.32.3
@@ -1,13 +1,13 @@
1
1
  jvserve/__init__.py,sha256=Jd0pamSDn2wGTZkNk8I9qNYTFBHp7rasdYO0_Dvad_k,245
2
- jvserve/cli.py,sha256=WTB_euv8R_cQ9fK_tlFHV_I6Jn-CYJrHKzjHsWSyWPY,9304
2
+ jvserve/cli.py,sha256=BThAXt_0csFd7Z6Hytr8usQ7gh-zfnqmh9kLWvVVKZc,12861
3
3
  jvserve/lib/__init__.py,sha256=cnzfSHLoTWG9Ygut2nOpDys5aPlQz-m0BSkB-nd7OMs,31
4
4
  jvserve/lib/agent_interface.py,sha256=Igv5Jb7i9Aq_7IbLDZ6jnldGKssAWKeb6iXoolX8u4k,3478
5
5
  jvserve/lib/file_interface.py,sha256=VO9RBCtJwaBxu5eZjc57-uRbsVXXZt86wVRVq9R3KXY,6079
6
6
  jvserve/lib/jac_interface.py,sha256=ydhXfYTsrhdvMXBTAd_vnAXJSSVBydQ3qavPU1-oodU,7973
7
7
  jvserve/lib/jvlogger.py,sha256=RNiB9PHuBzTvNIQWhxoDgrDlNYA0PYm1SVpvzlqu8mE,4180
8
- jvserve-2.1.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
9
- jvserve-2.1.2.dist-info/METADATA,sha256=EO9HyWpAhJym2eK2j9ZQVdFDvgVDBxYBL-fbrVVtLUw,4814
10
- jvserve-2.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
- jvserve-2.1.2.dist-info/entry_points.txt,sha256=HYyg1QXoLs0JRb004L300VeLOZyDLY27ynD1tnTnEN4,35
12
- jvserve-2.1.2.dist-info/top_level.txt,sha256=afoCXZv-zXNBuhVIvfJGjafXKEiJl_ooy4BtgQwAG4Q,8
13
- jvserve-2.1.2.dist-info/RECORD,,
8
+ jvserve-2.1.4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
9
+ jvserve-2.1.4.dist-info/METADATA,sha256=Lw8imqzvI0CWx5n7UT0LRjhDD_okwH_Of49IN37W8JI,4843
10
+ jvserve-2.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ jvserve-2.1.4.dist-info/entry_points.txt,sha256=HYyg1QXoLs0JRb004L300VeLOZyDLY27ynD1tnTnEN4,35
12
+ jvserve-2.1.4.dist-info/top_level.txt,sha256=afoCXZv-zXNBuhVIvfJGjafXKEiJl_ooy4BtgQwAG4Q,8
13
+ jvserve-2.1.4.dist-info/RECORD,,