jvserve 2.0.3__py3-none-any.whl → 2.0.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/__init__.py +1 -1
- jvserve/cli.py +19 -0
- jvserve/lib/file_interface.py +170 -0
- {jvserve-2.0.3.dist-info → jvserve-2.0.4.dist-info}/METADATA +4 -2
- jvserve-2.0.4.dist-info/RECORD +13 -0
- {jvserve-2.0.3.dist-info → jvserve-2.0.4.dist-info}/WHEEL +1 -1
- jvserve-2.0.3.dist-info/RECORD +0 -12
- {jvserve-2.0.3.dist-info → jvserve-2.0.4.dist-info}/entry_points.txt +0 -0
- {jvserve-2.0.3.dist-info → jvserve-2.0.4.dist-info/licenses}/LICENSE +0 -0
- {jvserve-2.0.3.dist-info → jvserve-2.0.4.dist-info}/top_level.txt +0 -0
jvserve/__init__.py
CHANGED
jvserve/cli.py
CHANGED
|
@@ -116,6 +116,7 @@ class JacCmd:
|
|
|
116
116
|
"""Launch the file server."""
|
|
117
117
|
# load FastAPI
|
|
118
118
|
from fastapi import FastAPI
|
|
119
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
119
120
|
from fastapi.staticfiles import StaticFiles
|
|
120
121
|
|
|
121
122
|
if directory:
|
|
@@ -126,6 +127,24 @@ class JacCmd:
|
|
|
126
127
|
|
|
127
128
|
# Setup custom routes
|
|
128
129
|
app = FastAPI()
|
|
130
|
+
|
|
131
|
+
# Add CORS middleware
|
|
132
|
+
app.add_middleware(
|
|
133
|
+
CORSMiddleware,
|
|
134
|
+
allow_origins=["*"],
|
|
135
|
+
allow_credentials=True,
|
|
136
|
+
allow_methods=["*"],
|
|
137
|
+
allow_headers=["*"],
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
app.mount(
|
|
141
|
+
"/files",
|
|
142
|
+
StaticFiles(
|
|
143
|
+
directory=os.environ.get("JIVAS_FILES_ROOT_PATH", ".files")
|
|
144
|
+
),
|
|
145
|
+
name="files",
|
|
146
|
+
)
|
|
147
|
+
|
|
129
148
|
app.mount(
|
|
130
149
|
"/files",
|
|
131
150
|
StaticFiles(
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File interface module with base class for making file handling configurable
|
|
3
|
+
for different storage backends.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
|
|
10
|
+
# Interface type determined by environment variable, defaults to local
|
|
11
|
+
FILE_INTERFACE = os.environ.get("JIVAS_FILE_INTERFACE", "local")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FileInterface(ABC):
|
|
15
|
+
"""Abstract base class defining the interface for file storage operations."""
|
|
16
|
+
|
|
17
|
+
__root_dir: str = ""
|
|
18
|
+
LOGGER: logging.Logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def get_file(self, filename: str) -> bytes | None:
|
|
22
|
+
"""Retrieve a file from storage and return its contents as bytes."""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def save_file(self, filename: str, content: bytes) -> bool:
|
|
27
|
+
"""Save content to a file in storage."""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def delete_file(self, filename: str) -> bool:
|
|
32
|
+
"""Delete a file from storage."""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def get_file_url(self, filename: str) -> str | None:
|
|
37
|
+
"""Get a URL to access the file."""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class LocalFileInterface(FileInterface):
|
|
42
|
+
"""Implementation of FileInterface for local filesystem storage."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, files_root: str = "") -> None:
|
|
45
|
+
"""Initialize local file interface with root directory."""
|
|
46
|
+
self.__root_dir = files_root
|
|
47
|
+
|
|
48
|
+
def get_file(self, filename: str) -> bytes | None:
|
|
49
|
+
"""Read and return contents of local file."""
|
|
50
|
+
file_path = os.path.join(self.__root_dir, filename)
|
|
51
|
+
if os.path.exists(file_path):
|
|
52
|
+
with open(file_path, "rb") as f:
|
|
53
|
+
return f.read()
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
def save_file(self, filename: str, content: bytes) -> bool:
|
|
57
|
+
"""Write content to a local file."""
|
|
58
|
+
file_path = os.path.join(self.__root_dir, filename)
|
|
59
|
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
60
|
+
with open(file_path, "wb") as f:
|
|
61
|
+
f.write(content)
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
def delete_file(self, filename: str) -> bool:
|
|
65
|
+
"""Delete local file."""
|
|
66
|
+
file_path = os.path.join(self.__root_dir, filename)
|
|
67
|
+
if os.path.exists(file_path):
|
|
68
|
+
os.remove(file_path)
|
|
69
|
+
return True
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
def get_file_url(self, filename: str) -> str | None:
|
|
73
|
+
"""Get URL for accessing local file via HTTP."""
|
|
74
|
+
file_path = os.path.join(self.__root_dir, filename)
|
|
75
|
+
if os.path.exists(file_path):
|
|
76
|
+
return f"{os.environ.get('JIVAS_FILES_URL', 'http://localhost:9000/files')}/{filename}"
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class S3FileInterface(FileInterface):
|
|
81
|
+
"""Implementation of FileInterface for AWS S3 storage."""
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
bucket_name: str,
|
|
86
|
+
aws_access_key_id: str,
|
|
87
|
+
aws_secret_access_key: str,
|
|
88
|
+
region_name: str,
|
|
89
|
+
endpoint_url: str | None = None,
|
|
90
|
+
files_root: str = ".files",
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Initialize S3 file interface."""
|
|
93
|
+
import boto3
|
|
94
|
+
from botocore.config import Config
|
|
95
|
+
|
|
96
|
+
self.s3_client = boto3.client(
|
|
97
|
+
"s3",
|
|
98
|
+
aws_access_key_id=aws_access_key_id,
|
|
99
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
100
|
+
region_name=region_name,
|
|
101
|
+
endpoint_url=endpoint_url,
|
|
102
|
+
config=Config(signature_version="v4"),
|
|
103
|
+
)
|
|
104
|
+
self.bucket_name = bucket_name
|
|
105
|
+
self.__root_dir = files_root
|
|
106
|
+
|
|
107
|
+
# Check for missing AWS credentials
|
|
108
|
+
if not aws_access_key_id or not aws_secret_access_key or not region_name:
|
|
109
|
+
FileInterface.LOGGER.warn(
|
|
110
|
+
"Missing AWS credentials - S3 operations may fail"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def get_file(self, filename: str) -> bytes | None:
|
|
114
|
+
"""Get file contents from S3."""
|
|
115
|
+
try:
|
|
116
|
+
file_key = os.path.join(self.__root_dir, filename)
|
|
117
|
+
response = self.s3_client.get_object(Bucket=self.bucket_name, Key=file_key)
|
|
118
|
+
return response["Body"].read()
|
|
119
|
+
except Exception:
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
def save_file(self, filename: str, content: bytes) -> bool:
|
|
123
|
+
"""Save file to S3 bucket."""
|
|
124
|
+
try:
|
|
125
|
+
file_key = os.path.join(self.__root_dir, filename)
|
|
126
|
+
self.s3_client.put_object(
|
|
127
|
+
Bucket=self.bucket_name, Key=file_key, Body=content
|
|
128
|
+
)
|
|
129
|
+
return True
|
|
130
|
+
except Exception:
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
def delete_file(self, filename: str) -> bool:
|
|
134
|
+
"""Delete file from S3 bucket."""
|
|
135
|
+
try:
|
|
136
|
+
file_key = os.path.join(self.__root_dir, filename)
|
|
137
|
+
self.s3_client.delete_object(Bucket=self.bucket_name, Key=file_key)
|
|
138
|
+
return True
|
|
139
|
+
except Exception:
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
def get_file_url(self, filename: str) -> str | None:
|
|
143
|
+
"""Get pre-signed URL for S3 file access."""
|
|
144
|
+
try:
|
|
145
|
+
file_key = os.path.join(self.__root_dir, filename)
|
|
146
|
+
url = self.s3_client.generate_presigned_url(
|
|
147
|
+
"get_object",
|
|
148
|
+
Params={"Bucket": self.bucket_name, "Key": file_key},
|
|
149
|
+
ExpiresIn=3600,
|
|
150
|
+
)
|
|
151
|
+
return url
|
|
152
|
+
except Exception:
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
file_interface: FileInterface
|
|
157
|
+
|
|
158
|
+
if FILE_INTERFACE == "s3":
|
|
159
|
+
file_interface = S3FileInterface(
|
|
160
|
+
bucket_name=os.environ.get("JIVAS_S3_BUCKET_NAME", ""),
|
|
161
|
+
region_name=os.environ.get("JIVAS_S3_REGION_NAME", "us-east-1"),
|
|
162
|
+
aws_access_key_id=os.environ.get("JIVAS_S3_ACCESS_KEY_ID", ""),
|
|
163
|
+
aws_secret_access_key=os.environ.get("JIVAS_S3_SECRET_ACCESS_KEY", ""),
|
|
164
|
+
endpoint_url=os.environ.get("JIVAS_S3_ENDPOINT_URL"),
|
|
165
|
+
files_root=os.environ.get("JIVAS_FILES_ROOT_PATH", ".files"),
|
|
166
|
+
)
|
|
167
|
+
else:
|
|
168
|
+
file_interface = LocalFileInterface(
|
|
169
|
+
files_root=os.environ.get("JIVAS_FILES_ROOT_PATH", ".files")
|
|
170
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: jvserve
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.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.
|
|
@@ -14,6 +14,7 @@ Requires-Dist: pyaml>=25.1.0
|
|
|
14
14
|
Requires-Dist: requests>=2.32.3
|
|
15
15
|
Requires-Dist: aiohttp>=3.10.10
|
|
16
16
|
Requires-Dist: schedule>=1.2.2
|
|
17
|
+
Requires-Dist: boto3>=1.37.10
|
|
17
18
|
Provides-Extra: dev
|
|
18
19
|
Requires-Dist: pre-commit; extra == "dev"
|
|
19
20
|
Requires-Dist: pytest; extra == "dev"
|
|
@@ -26,6 +27,7 @@ Dynamic: description
|
|
|
26
27
|
Dynamic: description-content-type
|
|
27
28
|
Dynamic: home-page
|
|
28
29
|
Dynamic: keywords
|
|
30
|
+
Dynamic: license-file
|
|
29
31
|
Dynamic: provides-extra
|
|
30
32
|
Dynamic: requires-dist
|
|
31
33
|
Dynamic: requires-python
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
jvserve/__init__.py,sha256=7UFcPqavIGuuk32gYeDVWCgMuafw020dneTSbvQ4JQI,190
|
|
2
|
+
jvserve/cli.py,sha256=oeWCqA9MAqMYST506f8Nv22qs91OSy555V-NoP-pNOM,4942
|
|
3
|
+
jvserve/lib/__init__.py,sha256=cnzfSHLoTWG9Ygut2nOpDys5aPlQz-m0BSkB-nd7OMs,31
|
|
4
|
+
jvserve/lib/agent_interface.py,sha256=WFHAxCNbkRhqrOsycRnMtPNOQxHvKbQ4oOifz30WhxE,25801
|
|
5
|
+
jvserve/lib/agent_pulse.py,sha256=6hBF6KQYr6Z9Mi_yoWKGfdnW7gg84kK20Slu-bLR_m8,2067
|
|
6
|
+
jvserve/lib/file_interface.py,sha256=lnNOhV9GEmet3-uDQw2hBbvdpnB3-U7amFuR80nv04g,5819
|
|
7
|
+
jvserve/lib/jvlogger.py,sha256=RNiB9PHuBzTvNIQWhxoDgrDlNYA0PYm1SVpvzlqu8mE,4180
|
|
8
|
+
jvserve-2.0.4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
9
|
+
jvserve-2.0.4.dist-info/METADATA,sha256=3ADQS-pw8H6v2WCuKWWAb1jz3r3FlAWiY1lWzVwi9Ks,4790
|
|
10
|
+
jvserve-2.0.4.dist-info/WHEEL,sha256=tTnHoFhvKQHCh4jz3yCn0WPTYIy7wXx3CJtJ7SJGV7c,91
|
|
11
|
+
jvserve-2.0.4.dist-info/entry_points.txt,sha256=HYyg1QXoLs0JRb004L300VeLOZyDLY27ynD1tnTnEN4,35
|
|
12
|
+
jvserve-2.0.4.dist-info/top_level.txt,sha256=afoCXZv-zXNBuhVIvfJGjafXKEiJl_ooy4BtgQwAG4Q,8
|
|
13
|
+
jvserve-2.0.4.dist-info/RECORD,,
|
jvserve-2.0.3.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
jvserve/__init__.py,sha256=kdBEnJ_yZSLFNvvfJpYP50Evuk1l96p16bGXn6rm5p0,190
|
|
2
|
-
jvserve/cli.py,sha256=KVWuGPlAt6Vzti-J8CbB4YsgTCc6gS3HaC2OElGqMD0,4392
|
|
3
|
-
jvserve/lib/__init__.py,sha256=cnzfSHLoTWG9Ygut2nOpDys5aPlQz-m0BSkB-nd7OMs,31
|
|
4
|
-
jvserve/lib/agent_interface.py,sha256=WFHAxCNbkRhqrOsycRnMtPNOQxHvKbQ4oOifz30WhxE,25801
|
|
5
|
-
jvserve/lib/agent_pulse.py,sha256=6hBF6KQYr6Z9Mi_yoWKGfdnW7gg84kK20Slu-bLR_m8,2067
|
|
6
|
-
jvserve/lib/jvlogger.py,sha256=RNiB9PHuBzTvNIQWhxoDgrDlNYA0PYm1SVpvzlqu8mE,4180
|
|
7
|
-
jvserve-2.0.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
8
|
-
jvserve-2.0.3.dist-info/METADATA,sha256=OH5Qi-X7LvSI3jXiybDO9JjTOY9PJw_0ejZhXAjzAvA,4738
|
|
9
|
-
jvserve-2.0.3.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
10
|
-
jvserve-2.0.3.dist-info/entry_points.txt,sha256=HYyg1QXoLs0JRb004L300VeLOZyDLY27ynD1tnTnEN4,35
|
|
11
|
-
jvserve-2.0.3.dist-info/top_level.txt,sha256=afoCXZv-zXNBuhVIvfJGjafXKEiJl_ooy4BtgQwAG4Q,8
|
|
12
|
-
jvserve-2.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|