jvserve 2.0.16__py3-none-any.whl → 2.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.
Potentially problematic release.
This version of jvserve might be problematic. Click here for more details.
- jvserve/__init__.py +4 -2
- jvserve/cli.py +207 -221
- jvserve/lib/agent_interface.py +67 -871
- jvserve/lib/file_interface.py +1 -1
- jvserve/lib/jac_interface.py +222 -0
- {jvserve-2.0.16.dist-info → jvserve-2.1.1.dist-info}/METADATA +4 -3
- jvserve-2.1.1.dist-info/RECORD +13 -0
- jvserve/lib/agent_pulse.py +0 -63
- jvserve-2.0.16.dist-info/RECORD +0 -13
- {jvserve-2.0.16.dist-info → jvserve-2.1.1.dist-info}/WHEEL +0 -0
- {jvserve-2.0.16.dist-info → jvserve-2.1.1.dist-info}/entry_points.txt +0 -0
- {jvserve-2.0.16.dist-info → jvserve-2.1.1.dist-info}/licenses/LICENSE +0 -0
- {jvserve-2.0.16.dist-info → jvserve-2.1.1.dist-info}/top_level.txt +0 -0
jvserve/__init__.py
CHANGED
|
@@ -4,5 +4,7 @@ jvserve package initialization.
|
|
|
4
4
|
This package provides the webserver for loading and interacting with JIVAS agents.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
from importlib.metadata import version
|
|
8
|
+
|
|
9
|
+
__version__ = version("jvserve")
|
|
10
|
+
__supported__jivas__versions__ = [__version__]
|
jvserve/cli.py
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
"""Module for registering CLI plugins for jaseci."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import logging
|
|
4
5
|
import os
|
|
5
|
-
import
|
|
6
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
6
7
|
from contextlib import asynccontextmanager
|
|
8
|
+
from pickle import load
|
|
7
9
|
from typing import AsyncIterator, Optional
|
|
8
10
|
|
|
11
|
+
import aiohttp
|
|
12
|
+
import pymongo
|
|
13
|
+
from bson import ObjectId
|
|
9
14
|
from dotenv import load_dotenv
|
|
10
|
-
from fastapi
|
|
11
|
-
from
|
|
15
|
+
from fastapi import FastAPI, HTTPException, Response
|
|
16
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
17
|
+
from fastapi.responses import FileResponse, StreamingResponse
|
|
18
|
+
from jac_cloud.core.context import JaseciContext
|
|
19
|
+
from jac_cloud.jaseci.main import FastAPI as JaseciFastAPI # type: ignore
|
|
12
20
|
from jac_cloud.plugin.jaseci import NodeAnchor
|
|
21
|
+
from jaclang import JacMachine as Jac
|
|
13
22
|
from jaclang.cli.cmdreg import cmd_registry
|
|
14
|
-
from jaclang.
|
|
15
|
-
from
|
|
16
|
-
from jaclang.runtimelib.machine import JacMachine
|
|
17
|
-
from uvicorn import run as _run
|
|
23
|
+
from jaclang.runtimelib.machine import hookimpl
|
|
24
|
+
from watchfiles import Change, run_process
|
|
18
25
|
|
|
19
26
|
from jvserve.lib.agent_interface import AgentInterface
|
|
20
|
-
from jvserve.lib.agent_pulse import AgentPulse
|
|
21
27
|
from jvserve.lib.file_interface import (
|
|
22
28
|
DEFAULT_FILES_ROOT,
|
|
23
29
|
FILE_INTERFACE,
|
|
@@ -25,59 +31,207 @@ from jvserve.lib.file_interface import (
|
|
|
25
31
|
)
|
|
26
32
|
from jvserve.lib.jvlogger import JVLogger
|
|
27
33
|
|
|
34
|
+
# quiet the jac_cloud logger down to errors only
|
|
35
|
+
# jac cloud dumps payload details to console which makes it hard to debug in JIVAS
|
|
36
|
+
os.environ["LOGGER_LEVEL"] = "ERROR"
|
|
28
37
|
load_dotenv(".env")
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
# Set up logging
|
|
39
|
+
JVLogger.setup_logging(level="INFO")
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
# Global for MongoDB collection with thread-safe initialization
|
|
43
|
+
url_proxy_collection = None
|
|
44
|
+
collection_init_lock = asyncio.Lock()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def get_url_proxy_collection() -> pymongo.collection.Collection:
|
|
48
|
+
"""Thread-safe initialization of MongoDB collection"""
|
|
49
|
+
global url_proxy_collection
|
|
50
|
+
if url_proxy_collection is None:
|
|
51
|
+
async with collection_init_lock:
|
|
52
|
+
if url_proxy_collection is None: # Double-check locking
|
|
53
|
+
loop = asyncio.get_running_loop()
|
|
54
|
+
with ThreadPoolExecutor() as pool:
|
|
55
|
+
url_proxy_collection = await loop.run_in_executor(
|
|
56
|
+
pool,
|
|
57
|
+
lambda: NodeAnchor.Collection.get_collection("url_proxies"),
|
|
58
|
+
)
|
|
59
|
+
return url_proxy_collection
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def serve_proxied_file(
|
|
63
|
+
file_path: str,
|
|
64
|
+
) -> FileResponse | StreamingResponse | Response:
|
|
65
|
+
"""Serve a proxied file from a remote or local URL (async version)"""
|
|
38
66
|
if FILE_INTERFACE == "local":
|
|
39
|
-
|
|
67
|
+
root_path = os.environ.get("JIVAS_FILES_ROOT_PATH", DEFAULT_FILES_ROOT)
|
|
68
|
+
full_path = os.path.join(root_path, file_path)
|
|
69
|
+
if not os.path.exists(full_path):
|
|
70
|
+
raise HTTPException(status_code=404, detail="File not found")
|
|
71
|
+
return FileResponse(full_path)
|
|
40
72
|
|
|
41
73
|
file_url = file_interface.get_file_url(file_path)
|
|
42
74
|
|
|
75
|
+
# Security check to prevent recursive calls
|
|
43
76
|
if file_url and ("localhost" in file_url or "127.0.0.1" in file_url):
|
|
44
|
-
|
|
45
|
-
|
|
77
|
+
raise HTTPException(
|
|
78
|
+
status_code=500, detail="Environment misconfiguration detected"
|
|
79
|
+
)
|
|
46
80
|
|
|
47
81
|
if not file_url:
|
|
48
82
|
raise HTTPException(status_code=404, detail="File not found")
|
|
49
83
|
|
|
50
|
-
|
|
84
|
+
try:
|
|
85
|
+
async with aiohttp.ClientSession() as session:
|
|
86
|
+
async with session.get(file_url) as response:
|
|
87
|
+
response.raise_for_status()
|
|
88
|
+
|
|
89
|
+
return StreamingResponse(
|
|
90
|
+
response.content.iter_chunked(8192),
|
|
91
|
+
media_type=response.headers.get(
|
|
92
|
+
"Content-Type", "application/octet-stream"
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
except aiohttp.ClientError as e:
|
|
96
|
+
raise HTTPException(status_code=502, detail=f"File fetch error: {str(e)}")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def run_jivas(filename: str, host: str = "localhost", port: int = 8000) -> None:
|
|
100
|
+
"""Starts JIVAS server with integrated file services"""
|
|
101
|
+
|
|
102
|
+
# Create agent interface instance with configuration
|
|
103
|
+
agent_interface = AgentInterface.get_instance(host=host, port=port)
|
|
104
|
+
|
|
105
|
+
base, mod = os.path.split(filename)
|
|
106
|
+
base = base if base else "./"
|
|
107
|
+
mod = mod[:-4]
|
|
108
|
+
|
|
109
|
+
JaseciFastAPI.enable()
|
|
110
|
+
|
|
111
|
+
ctx = JaseciContext.create(None)
|
|
112
|
+
if filename.endswith(".jac"):
|
|
113
|
+
Jac.jac_import(target=mod, base_path=base, override_name="__main__")
|
|
114
|
+
elif filename.endswith(".jir"):
|
|
115
|
+
with open(filename, "rb") as f:
|
|
116
|
+
Jac.attach_program(load(f))
|
|
117
|
+
Jac.jac_import(target=mod, base_path=base, override_name="__main__")
|
|
118
|
+
else:
|
|
119
|
+
raise ValueError("Not a valid file!\nOnly supports `.jac` and `.jir`")
|
|
120
|
+
|
|
121
|
+
# Define post-startup function to run AFTER server is ready
|
|
122
|
+
async def post_startup() -> None:
|
|
123
|
+
"""Wait for server to be ready before initializing agents"""
|
|
124
|
+
health_url = f"http://{host}:{port}/healthz"
|
|
125
|
+
max_retries = 10
|
|
126
|
+
retry_delay = 1.0
|
|
127
|
+
|
|
128
|
+
for attempt in range(max_retries):
|
|
129
|
+
try:
|
|
130
|
+
async with aiohttp.ClientSession() as session:
|
|
131
|
+
async with session.get(health_url, timeout=1) as response:
|
|
132
|
+
if response.status == 200:
|
|
133
|
+
logger.info("Server is ready, initializing agents...")
|
|
134
|
+
await agent_interface.init_agents()
|
|
135
|
+
return
|
|
136
|
+
except (aiohttp.ClientConnectorError, asyncio.TimeoutError) as e:
|
|
137
|
+
logger.warning(
|
|
138
|
+
f"Server not ready yet (attempt {attempt + 1} / {max_retries}): {e}"
|
|
139
|
+
)
|
|
140
|
+
await asyncio.sleep(retry_delay)
|
|
141
|
+
retry_delay *= 1.5 # Exponential backoff
|
|
142
|
+
|
|
143
|
+
logger.error(
|
|
144
|
+
"Server did not become ready in time. Agent initialization skipped."
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# set up lifespan events
|
|
148
|
+
async def on_startup() -> None:
|
|
149
|
+
logger.info("JIVAS is starting up...")
|
|
150
|
+
# Start initialization in background without blocking
|
|
151
|
+
asyncio.create_task(post_startup())
|
|
152
|
+
|
|
153
|
+
async def on_shutdown() -> None:
|
|
154
|
+
logger.info("JIVAS is shutting down...")
|
|
155
|
+
|
|
156
|
+
app = JaseciFastAPI.get()
|
|
157
|
+
app_lifespan = app.router.lifespan_context
|
|
158
|
+
|
|
159
|
+
@asynccontextmanager
|
|
160
|
+
async def lifespan_wrapper(app: FastAPI) -> AsyncIterator[Optional[str]]:
|
|
161
|
+
await on_startup()
|
|
162
|
+
async with app_lifespan(app) as maybe_state:
|
|
163
|
+
yield maybe_state
|
|
164
|
+
await on_shutdown()
|
|
165
|
+
|
|
166
|
+
app.router.lifespan_context = lifespan_wrapper
|
|
167
|
+
|
|
168
|
+
# Add CORS middleware to main app
|
|
169
|
+
app.add_middleware(
|
|
170
|
+
CORSMiddleware,
|
|
171
|
+
allow_origins=["*"],
|
|
172
|
+
allow_credentials=True,
|
|
173
|
+
allow_methods=["*"],
|
|
174
|
+
allow_headers=["*"],
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Ensure the local file directory exists if that's the interface
|
|
178
|
+
if FILE_INTERFACE == "local":
|
|
179
|
+
directory = os.environ.get("JIVAS_FILES_ROOT_PATH", DEFAULT_FILES_ROOT)
|
|
180
|
+
if not os.path.exists(directory):
|
|
181
|
+
os.makedirs(directory, exist_ok=True)
|
|
182
|
+
|
|
183
|
+
# Setup file serving endpoint for both local and S3
|
|
184
|
+
@app.get("/files/{file_path:path}", response_model=None)
|
|
185
|
+
async def serve_file(
|
|
186
|
+
file_path: str,
|
|
187
|
+
) -> FileResponse | StreamingResponse | Response:
|
|
188
|
+
# The serve_proxied_file function already handles both local and S3 cases
|
|
189
|
+
return await serve_proxied_file(file_path)
|
|
190
|
+
|
|
191
|
+
# Setup URL proxy endpoint
|
|
192
|
+
@app.get("/f/{file_id:path}", response_model=None)
|
|
193
|
+
async def get_proxied_file(
|
|
194
|
+
file_id: str,
|
|
195
|
+
) -> FileResponse | StreamingResponse | Response:
|
|
196
|
+
params = file_id.split("/")
|
|
197
|
+
object_id = params[0]
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
# Get MongoDB collection (thread-safe initialization)
|
|
201
|
+
collection = await get_url_proxy_collection()
|
|
202
|
+
|
|
203
|
+
# Run blocking MongoDB operation in thread pool
|
|
204
|
+
loop = asyncio.get_running_loop()
|
|
205
|
+
file_details = await loop.run_in_executor(
|
|
206
|
+
None, lambda: collection.find_one({"_id": ObjectId(object_id)})
|
|
207
|
+
)
|
|
51
208
|
|
|
52
|
-
|
|
53
|
-
direct_serve_extensions = [
|
|
54
|
-
".pdf",
|
|
55
|
-
".html",
|
|
56
|
-
".txt",
|
|
57
|
-
".js",
|
|
58
|
-
".css",
|
|
59
|
-
".json",
|
|
60
|
-
".xml",
|
|
61
|
-
".svg",
|
|
62
|
-
".csv",
|
|
63
|
-
".ico",
|
|
64
|
-
]
|
|
209
|
+
descriptor_path = os.environ.get("JIVAS_DESCRIPTOR_ROOT_PATH")
|
|
65
210
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
211
|
+
if file_details:
|
|
212
|
+
if descriptor_path and descriptor_path in file_details["path"]:
|
|
213
|
+
return Response(status_code=403)
|
|
214
|
+
return await serve_proxied_file(file_details["path"])
|
|
69
215
|
|
|
70
|
-
|
|
216
|
+
raise HTTPException(status_code=404, detail="File not found")
|
|
217
|
+
except Exception as e:
|
|
218
|
+
logger.error(f"Proxy error: {str(e)}")
|
|
219
|
+
raise HTTPException(status_code=500, detail="Internal server error")
|
|
71
220
|
|
|
72
|
-
|
|
221
|
+
ctx.close()
|
|
222
|
+
# Run the app
|
|
223
|
+
JaseciFastAPI.start(host=host, port=port)
|
|
73
224
|
|
|
74
|
-
file_response = requests.get(file_url, stream=True)
|
|
75
|
-
file_response.raise_for_status()
|
|
76
225
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
226
|
+
def log_reload(changes: set[tuple[Change, str]]) -> None:
|
|
227
|
+
"""Log changes."""
|
|
228
|
+
num_of_changes = len(changes)
|
|
229
|
+
logger.warning(
|
|
230
|
+
f'Detected {num_of_changes} change{"s" if num_of_changes > 1 else ""}'
|
|
80
231
|
)
|
|
232
|
+
for change in changes:
|
|
233
|
+
logger.warning(f"{change[1]} ({change[0].name})")
|
|
234
|
+
logger.warning("Reloading ...")
|
|
81
235
|
|
|
82
236
|
|
|
83
237
|
class JacCmd:
|
|
@@ -89,182 +243,14 @@ class JacCmd:
|
|
|
89
243
|
"""Create Jac CLI cmds."""
|
|
90
244
|
|
|
91
245
|
@cmd_registry.register
|
|
92
|
-
def jvserve(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
loglevel: str = "INFO",
|
|
97
|
-
workers: Optional[int] = None,
|
|
98
|
-
) -> None:
|
|
99
|
-
"""Launch the jac application."""
|
|
100
|
-
from jaclang import jac_import
|
|
101
|
-
|
|
102
|
-
# set up logging
|
|
103
|
-
JVLogger.setup_logging(level=loglevel)
|
|
104
|
-
logger = logging.getLogger(__name__)
|
|
105
|
-
|
|
106
|
-
# load FastAPI
|
|
107
|
-
from jac_cloud import FastAPI
|
|
108
|
-
|
|
109
|
-
FastAPI.enable()
|
|
110
|
-
|
|
111
|
-
# load the JAC application
|
|
112
|
-
jctx = ExecutionContext.create()
|
|
113
|
-
|
|
114
|
-
base, mod = os.path.split(filename)
|
|
115
|
-
base = base if base else "./"
|
|
116
|
-
mod = mod[:-4]
|
|
117
|
-
|
|
118
|
-
if filename.endswith(".jac"):
|
|
119
|
-
start_time = time.time()
|
|
120
|
-
jac_import(
|
|
121
|
-
target=mod,
|
|
122
|
-
base_path=base,
|
|
123
|
-
cachable=True,
|
|
124
|
-
override_name="__main__",
|
|
125
|
-
)
|
|
126
|
-
logger.info(f"Loading took {time.time() - start_time} seconds")
|
|
127
|
-
|
|
128
|
-
AgentInterface.HOST = host
|
|
129
|
-
AgentInterface.PORT = port
|
|
130
|
-
|
|
131
|
-
# set up lifespan events
|
|
132
|
-
async def on_startup() -> None:
|
|
133
|
-
# Perform initialization actions here
|
|
134
|
-
logger.info("JIVAS is starting up...")
|
|
135
|
-
|
|
136
|
-
async def on_shutdown() -> None:
|
|
137
|
-
# Perform initialization actions here
|
|
138
|
-
logger.info("JIVAS is shutting down...")
|
|
139
|
-
AgentPulse.stop()
|
|
140
|
-
# await AgentRTC.on_shutdown()
|
|
141
|
-
jctx.close()
|
|
142
|
-
JacMachine.detach()
|
|
143
|
-
|
|
144
|
-
app_lifespan = FastAPI.get().router.lifespan_context
|
|
145
|
-
|
|
146
|
-
@asynccontextmanager
|
|
147
|
-
async def lifespan_wrapper(app: FastAPI) -> AsyncIterator[Optional[str]]:
|
|
148
|
-
await on_startup()
|
|
149
|
-
async with app_lifespan(app) as maybe_state:
|
|
150
|
-
yield maybe_state
|
|
151
|
-
await on_shutdown()
|
|
152
|
-
|
|
153
|
-
FastAPI.get().router.lifespan_context = lifespan_wrapper
|
|
154
|
-
|
|
155
|
-
# Setup custom routes
|
|
156
|
-
FastAPI.get().add_api_route(
|
|
157
|
-
"/interact", endpoint=AgentInterface.interact, methods=["POST"]
|
|
246
|
+
def jvserve(filename: str, host: str = "localhost", port: int = 8000) -> None:
|
|
247
|
+
"""Launch unified JIVAS server with file services"""
|
|
248
|
+
watchdir = os.path.join(
|
|
249
|
+
os.path.abspath(os.path.dirname(filename)), "actions", ""
|
|
158
250
|
)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
251
|
+
run_process(
|
|
252
|
+
watchdir,
|
|
253
|
+
target=run_jivas,
|
|
254
|
+
args=(filename, host, port),
|
|
255
|
+
callback=log_reload,
|
|
163
256
|
)
|
|
164
|
-
FastAPI.get().add_api_route(
|
|
165
|
-
"/action/walker",
|
|
166
|
-
endpoint=AgentInterface.action_walker_exec,
|
|
167
|
-
methods=["POST"],
|
|
168
|
-
dependencies=authenticator,
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
# run the app
|
|
172
|
-
_run(FastAPI.get(), host=host, port=port, lifespan="on", workers=workers)
|
|
173
|
-
|
|
174
|
-
@cmd_registry.register
|
|
175
|
-
def jvfileserve(
|
|
176
|
-
directory: str, host: str = "0.0.0.0", port: int = 9000
|
|
177
|
-
) -> None:
|
|
178
|
-
"""Launch the file server for local files."""
|
|
179
|
-
# load FastAPI
|
|
180
|
-
from fastapi import FastAPI
|
|
181
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
182
|
-
from fastapi.staticfiles import StaticFiles
|
|
183
|
-
|
|
184
|
-
# Setup custom routes
|
|
185
|
-
app = FastAPI()
|
|
186
|
-
|
|
187
|
-
# Add CORS middleware
|
|
188
|
-
app.add_middleware(
|
|
189
|
-
CORSMiddleware,
|
|
190
|
-
allow_origins=["*"],
|
|
191
|
-
allow_credentials=True,
|
|
192
|
-
allow_methods=["*"],
|
|
193
|
-
allow_headers=["*"],
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
if not os.path.exists(directory):
|
|
197
|
-
os.makedirs(directory)
|
|
198
|
-
|
|
199
|
-
# Set the environment variable for the file root path
|
|
200
|
-
os.environ["JIVAS_FILES_ROOT_PATH"] = directory
|
|
201
|
-
|
|
202
|
-
# Mount the static files directory
|
|
203
|
-
app.mount(
|
|
204
|
-
"/files",
|
|
205
|
-
StaticFiles(directory=directory),
|
|
206
|
-
name="files",
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
# run the app
|
|
210
|
-
_run(app, host=host, port=port)
|
|
211
|
-
|
|
212
|
-
@cmd_registry.register
|
|
213
|
-
def jvproxyserve(
|
|
214
|
-
directory: str, host: str = "0.0.0.0", port: int = 9000
|
|
215
|
-
) -> None:
|
|
216
|
-
"""Launch the file proxy server for remote files."""
|
|
217
|
-
# load FastAPI
|
|
218
|
-
from fastapi import FastAPI
|
|
219
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
220
|
-
|
|
221
|
-
# Setup custom routes
|
|
222
|
-
app = FastAPI()
|
|
223
|
-
|
|
224
|
-
# Add CORS middleware
|
|
225
|
-
app.add_middleware(
|
|
226
|
-
CORSMiddleware,
|
|
227
|
-
allow_origins=["*"],
|
|
228
|
-
allow_credentials=True,
|
|
229
|
-
allow_methods=["*"],
|
|
230
|
-
allow_headers=["*"],
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
# Add proxy routes only if using S3
|
|
234
|
-
if FILE_INTERFACE == "s3":
|
|
235
|
-
|
|
236
|
-
@app.get("/files/{file_path:path}", response_model=None)
|
|
237
|
-
async def serve_file(
|
|
238
|
-
file_path: str,
|
|
239
|
-
) -> FileResponse | StreamingResponse | Response:
|
|
240
|
-
descriptor_path = os.environ["JIVAS_DESCRIPTOR_ROOT_PATH"]
|
|
241
|
-
if descriptor_path and descriptor_path in file_path:
|
|
242
|
-
return Response(status_code=403)
|
|
243
|
-
|
|
244
|
-
return serve_proxied_file(file_path)
|
|
245
|
-
|
|
246
|
-
@app.get("/f/{file_id:path}", response_model=None)
|
|
247
|
-
async def get_proxied_file(
|
|
248
|
-
file_id: str,
|
|
249
|
-
) -> FileResponse | StreamingResponse | Response:
|
|
250
|
-
from bson import ObjectId
|
|
251
|
-
from fastapi import HTTPException
|
|
252
|
-
|
|
253
|
-
params = file_id.split("/")
|
|
254
|
-
object_id = params[0]
|
|
255
|
-
|
|
256
|
-
# mongo db collection
|
|
257
|
-
collection = NodeAnchor.Collection.get_collection("url_proxies")
|
|
258
|
-
file_details = collection.find_one({"_id": ObjectId(object_id)})
|
|
259
|
-
descriptor_path = os.environ["JIVAS_DESCRIPTOR_ROOT_PATH"]
|
|
260
|
-
|
|
261
|
-
if file_details:
|
|
262
|
-
if descriptor_path and descriptor_path in file_details["path"]:
|
|
263
|
-
return Response(status_code=403)
|
|
264
|
-
|
|
265
|
-
return serve_proxied_file(file_details["path"])
|
|
266
|
-
|
|
267
|
-
raise HTTPException(status_code=404, detail="File not found")
|
|
268
|
-
|
|
269
|
-
# run the app
|
|
270
|
-
_run(app, host=host, port=port)
|