xenfra-sdk 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.
- xenfra_sdk/__init__.py +21 -21
- xenfra_sdk/cli/main.py +226 -226
- xenfra_sdk/client.py +4 -3
- xenfra_sdk/client_with_hooks.py +4 -2
- xenfra_sdk/config.py +26 -26
- xenfra_sdk/db/models.py +24 -27
- xenfra_sdk/db/session.py +30 -30
- xenfra_sdk/dependencies.py +39 -38
- xenfra_sdk/dockerizer.py +87 -87
- xenfra_sdk/engine.py +411 -388
- xenfra_sdk/exceptions.py +19 -19
- xenfra_sdk/mcp_client.py +154 -154
- xenfra_sdk/models.py +182 -170
- xenfra_sdk/patterns.json +13 -13
- xenfra_sdk/privacy.py +153 -151
- xenfra_sdk/recipes.py +25 -25
- xenfra_sdk/resources/base.py +3 -3
- xenfra_sdk/resources/deployments.py +89 -85
- xenfra_sdk/resources/intelligence.py +95 -105
- xenfra_sdk/resources/projects.py +5 -10
- xenfra_sdk/security.py +41 -41
- xenfra_sdk/templates/Dockerfile.j2 +25 -25
- xenfra_sdk/templates/cloud-init.sh.j2 +68 -68
- xenfra_sdk/templates/docker-compose.yml.j2 +33 -33
- xenfra_sdk/utils.py +2 -4
- {xenfra_sdk-0.1.1.dist-info → xenfra_sdk-0.1.3.dist-info}/METADATA +92 -92
- xenfra_sdk-0.1.3.dist-info/RECORD +31 -0
- {xenfra_sdk-0.1.1.dist-info → xenfra_sdk-0.1.3.dist-info}/WHEEL +1 -1
- xenfra_sdk-0.1.1.dist-info/RECORD +0 -31
xenfra_sdk/db/session.py
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
# src/xenfra/db/session.py
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
import click
|
|
7
|
-
from sqlmodel import Session, SQLModel, create_engine
|
|
8
|
-
|
|
9
|
-
# Get the app directory for the current user
|
|
10
|
-
app_dir = Path(click.get_app_dir("xenfra"))
|
|
11
|
-
app_dir.mkdir(parents=True, exist_ok=True)
|
|
12
|
-
db_path = app_dir / "xenfra.db"
|
|
13
|
-
|
|
14
|
-
# For now, we will use a simple SQLite database for ease of setup.
|
|
15
|
-
# In production, this should be a PostgreSQL database URL from environment variables.
|
|
16
|
-
DATABASE_URL = os.getenv("DATABASE_URL", f"sqlite:///{db_path}")
|
|
17
|
-
|
|
18
|
-
# Only echo SQL in development (set SQL_ECHO=1 to enable)
|
|
19
|
-
SQL_ECHO = os.getenv("SQL_ECHO", "0") == "1"
|
|
20
|
-
|
|
21
|
-
engine = create_engine(DATABASE_URL, echo=SQL_ECHO)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def create_db_and_tables():
|
|
25
|
-
SQLModel.metadata.create_all(engine)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def get_session():
|
|
29
|
-
with Session(engine) as session:
|
|
30
|
-
yield session
|
|
1
|
+
# src/xenfra/db/session.py
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from sqlmodel import Session, SQLModel, create_engine
|
|
8
|
+
|
|
9
|
+
# Get the app directory for the current user
|
|
10
|
+
app_dir = Path(click.get_app_dir("xenfra"))
|
|
11
|
+
app_dir.mkdir(parents=True, exist_ok=True)
|
|
12
|
+
db_path = app_dir / "xenfra.db"
|
|
13
|
+
|
|
14
|
+
# For now, we will use a simple SQLite database for ease of setup.
|
|
15
|
+
# In production, this should be a PostgreSQL database URL from environment variables.
|
|
16
|
+
DATABASE_URL = os.getenv("DATABASE_URL", f"sqlite:///{db_path}")
|
|
17
|
+
|
|
18
|
+
# Only echo SQL in development (set SQL_ECHO=1 to enable)
|
|
19
|
+
SQL_ECHO = os.getenv("SQL_ECHO", "0") == "1"
|
|
20
|
+
|
|
21
|
+
engine = create_engine(DATABASE_URL, echo=SQL_ECHO)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def create_db_and_tables():
|
|
25
|
+
SQLModel.metadata.create_all(engine)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_session():
|
|
29
|
+
with Session(engine) as session:
|
|
30
|
+
yield session
|
xenfra_sdk/dependencies.py
CHANGED
|
@@ -1,38 +1,39 @@
|
|
|
1
|
-
# src/xenfra/dependencies.py
|
|
2
|
-
|
|
3
|
-
from fastapi import Depends, HTTPException, status
|
|
4
|
-
from fastapi.security import OAuth2PasswordBearer
|
|
5
|
-
from sqlmodel import Session, select
|
|
6
|
-
|
|
7
|
-
from xenfra.db.
|
|
8
|
-
from xenfra.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
# src/xenfra/dependencies.py
|
|
2
|
+
|
|
3
|
+
from fastapi import Depends, HTTPException, status
|
|
4
|
+
from fastapi.security import OAuth2PasswordBearer
|
|
5
|
+
from sqlmodel import Session, select
|
|
6
|
+
|
|
7
|
+
from xenfra.db.models import User
|
|
8
|
+
from xenfra.db.session import get_session
|
|
9
|
+
from xenfra.security import decode_token
|
|
10
|
+
|
|
11
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_current_user(
|
|
15
|
+
token: str = Depends(oauth2_scheme), session: Session = Depends(get_session)
|
|
16
|
+
) -> User:
|
|
17
|
+
credentials_exception = HTTPException(
|
|
18
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
19
|
+
detail="Could not validate credentials",
|
|
20
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
21
|
+
)
|
|
22
|
+
payload = decode_token(token)
|
|
23
|
+
if payload is None:
|
|
24
|
+
raise credentials_exception
|
|
25
|
+
|
|
26
|
+
email: str = payload.get("sub")
|
|
27
|
+
if email is None:
|
|
28
|
+
raise credentials_exception
|
|
29
|
+
|
|
30
|
+
user = session.exec(select(User).where(User.email == email)).first()
|
|
31
|
+
if user is None:
|
|
32
|
+
raise credentials_exception
|
|
33
|
+
return user
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_current_active_user(current_user: User = Depends(get_current_user)) -> User:
|
|
37
|
+
if not current_user.is_active:
|
|
38
|
+
raise HTTPException(status_code=400, detail="Inactive user")
|
|
39
|
+
return current_user
|
xenfra_sdk/dockerizer.py
CHANGED
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
from jinja2 import Environment, FileSystemLoader
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def detect_framework(path="."):
|
|
8
|
-
"""
|
|
9
|
-
Scans common Python project structures to guess the framework and entrypoint.
|
|
10
|
-
Returns: (framework_name, default_port, start_command) or (None, None, None)
|
|
11
|
-
"""
|
|
12
|
-
project_root = Path(path).resolve()
|
|
13
|
-
|
|
14
|
-
# Check for Django first (common pattern: manage.py in root)
|
|
15
|
-
if (project_root / "manage.py").is_file():
|
|
16
|
-
project_name = project_root.name
|
|
17
|
-
return "django", 8000, f"gunicorn {project_name}.wsgi:application --bind 0.0.0.0:8000"
|
|
18
|
-
|
|
19
|
-
candidate_files = []
|
|
20
|
-
|
|
21
|
-
# Check directly in project root
|
|
22
|
-
for name in ["main.py", "app.py"]:
|
|
23
|
-
if (project_root / name).is_file():
|
|
24
|
-
candidate_files.append(project_root / name)
|
|
25
|
-
|
|
26
|
-
# Check in src/*/ (standard package layout)
|
|
27
|
-
for src_dir in project_root.glob("src/*"):
|
|
28
|
-
if src_dir.is_dir():
|
|
29
|
-
for name in ["main.py", "app.py"]:
|
|
30
|
-
if (src_dir / name).is_file():
|
|
31
|
-
candidate_files.append(src_dir / name)
|
|
32
|
-
|
|
33
|
-
for file_path in candidate_files:
|
|
34
|
-
with open(file_path, "r") as f:
|
|
35
|
-
content = f.read()
|
|
36
|
-
|
|
37
|
-
module_name = str(file_path.relative_to(project_root)).replace(os.sep, ".")[:-3]
|
|
38
|
-
if module_name.startswith("src."):
|
|
39
|
-
module_name = module_name[4:]
|
|
40
|
-
|
|
41
|
-
if "FastAPI" in content:
|
|
42
|
-
return "fastapi", 8000, f"uvicorn {module_name}:app --host 0.0.0.0 --port 8000"
|
|
43
|
-
|
|
44
|
-
if "Flask" in content:
|
|
45
|
-
return "flask", 5000, f"gunicorn {module_name}:app -b 0.0.0.0:5000"
|
|
46
|
-
|
|
47
|
-
return None, None, None
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def generate_templated_assets(context: dict):
|
|
51
|
-
"""
|
|
52
|
-
Generates deployment assets (Dockerfile, docker-compose.yml) using Jinja2 templates.
|
|
53
|
-
|
|
54
|
-
Args:
|
|
55
|
-
context: A dictionary containing information for rendering templates,
|
|
56
|
-
e.g., {'database': 'postgres', 'python_version': 'python:3.11-slim'}
|
|
57
|
-
"""
|
|
58
|
-
# Path to the templates directory
|
|
59
|
-
template_dir = Path(__file__).parent / "templates"
|
|
60
|
-
env = Environment(loader=FileSystemLoader(template_dir))
|
|
61
|
-
|
|
62
|
-
# Detect framework specifics
|
|
63
|
-
framework, port, command = detect_framework()
|
|
64
|
-
if not framework:
|
|
65
|
-
print("Warning: No recognizable web framework detected.")
|
|
66
|
-
return []
|
|
67
|
-
|
|
68
|
-
# Merge detected context with provided context
|
|
69
|
-
render_context = {"port": port, "command": command, **context}
|
|
70
|
-
|
|
71
|
-
generated_files = []
|
|
72
|
-
|
|
73
|
-
# --- 1. Dockerfile ---
|
|
74
|
-
dockerfile_template = env.get_template("Dockerfile.j2")
|
|
75
|
-
dockerfile_content = dockerfile_template.render(render_context)
|
|
76
|
-
with open("Dockerfile", "w") as f:
|
|
77
|
-
f.write(dockerfile_content)
|
|
78
|
-
generated_files.append("Dockerfile")
|
|
79
|
-
|
|
80
|
-
# --- 2. docker-compose.yml ---
|
|
81
|
-
compose_template = env.get_template("docker-compose.yml.j2")
|
|
82
|
-
compose_content = compose_template.render(render_context)
|
|
83
|
-
with open("docker-compose.yml", "w") as f:
|
|
84
|
-
f.write(compose_content)
|
|
85
|
-
generated_files.append("docker-compose.yml")
|
|
86
|
-
|
|
87
|
-
return generated_files
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from jinja2 import Environment, FileSystemLoader
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def detect_framework(path="."):
|
|
8
|
+
"""
|
|
9
|
+
Scans common Python project structures to guess the framework and entrypoint.
|
|
10
|
+
Returns: (framework_name, default_port, start_command) or (None, None, None)
|
|
11
|
+
"""
|
|
12
|
+
project_root = Path(path).resolve()
|
|
13
|
+
|
|
14
|
+
# Check for Django first (common pattern: manage.py in root)
|
|
15
|
+
if (project_root / "manage.py").is_file():
|
|
16
|
+
project_name = project_root.name
|
|
17
|
+
return "django", 8000, f"gunicorn {project_name}.wsgi:application --bind 0.0.0.0:8000"
|
|
18
|
+
|
|
19
|
+
candidate_files = []
|
|
20
|
+
|
|
21
|
+
# Check directly in project root
|
|
22
|
+
for name in ["main.py", "app.py"]:
|
|
23
|
+
if (project_root / name).is_file():
|
|
24
|
+
candidate_files.append(project_root / name)
|
|
25
|
+
|
|
26
|
+
# Check in src/*/ (standard package layout)
|
|
27
|
+
for src_dir in project_root.glob("src/*"):
|
|
28
|
+
if src_dir.is_dir():
|
|
29
|
+
for name in ["main.py", "app.py"]:
|
|
30
|
+
if (src_dir / name).is_file():
|
|
31
|
+
candidate_files.append(src_dir / name)
|
|
32
|
+
|
|
33
|
+
for file_path in candidate_files:
|
|
34
|
+
with open(file_path, "r") as f:
|
|
35
|
+
content = f.read()
|
|
36
|
+
|
|
37
|
+
module_name = str(file_path.relative_to(project_root)).replace(os.sep, ".")[:-3]
|
|
38
|
+
if module_name.startswith("src."):
|
|
39
|
+
module_name = module_name[4:]
|
|
40
|
+
|
|
41
|
+
if "FastAPI" in content:
|
|
42
|
+
return "fastapi", 8000, f"uvicorn {module_name}:app --host 0.0.0.0 --port 8000"
|
|
43
|
+
|
|
44
|
+
if "Flask" in content:
|
|
45
|
+
return "flask", 5000, f"gunicorn {module_name}:app -b 0.0.0.0:5000"
|
|
46
|
+
|
|
47
|
+
return None, None, None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def generate_templated_assets(context: dict):
|
|
51
|
+
"""
|
|
52
|
+
Generates deployment assets (Dockerfile, docker-compose.yml) using Jinja2 templates.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
context: A dictionary containing information for rendering templates,
|
|
56
|
+
e.g., {'database': 'postgres', 'python_version': 'python:3.11-slim'}
|
|
57
|
+
"""
|
|
58
|
+
# Path to the templates directory
|
|
59
|
+
template_dir = Path(__file__).parent / "templates"
|
|
60
|
+
env = Environment(loader=FileSystemLoader(template_dir))
|
|
61
|
+
|
|
62
|
+
# Detect framework specifics
|
|
63
|
+
framework, port, command = detect_framework()
|
|
64
|
+
if not framework:
|
|
65
|
+
print("Warning: No recognizable web framework detected.")
|
|
66
|
+
return []
|
|
67
|
+
|
|
68
|
+
# Merge detected context with provided context
|
|
69
|
+
render_context = {"port": port, "command": command, **context}
|
|
70
|
+
|
|
71
|
+
generated_files = []
|
|
72
|
+
|
|
73
|
+
# --- 1. Dockerfile ---
|
|
74
|
+
dockerfile_template = env.get_template("Dockerfile.j2")
|
|
75
|
+
dockerfile_content = dockerfile_template.render(render_context)
|
|
76
|
+
with open("Dockerfile", "w") as f:
|
|
77
|
+
f.write(dockerfile_content)
|
|
78
|
+
generated_files.append("Dockerfile")
|
|
79
|
+
|
|
80
|
+
# --- 2. docker-compose.yml ---
|
|
81
|
+
compose_template = env.get_template("docker-compose.yml.j2")
|
|
82
|
+
compose_content = compose_template.render(render_context)
|
|
83
|
+
with open("docker-compose.yml", "w") as f:
|
|
84
|
+
f.write(compose_content)
|
|
85
|
+
generated_files.append("docker-compose.yml")
|
|
86
|
+
|
|
87
|
+
return generated_files
|