xenfra-sdk 0.2.5__py3-none-any.whl → 0.2.7__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 +46 -2
- xenfra_sdk/blueprints/base.py +150 -0
- xenfra_sdk/blueprints/factory.py +99 -0
- xenfra_sdk/blueprints/node.py +219 -0
- xenfra_sdk/blueprints/python.py +57 -0
- xenfra_sdk/blueprints/railpack.py +99 -0
- xenfra_sdk/blueprints/schema.py +70 -0
- xenfra_sdk/cli/main.py +175 -49
- xenfra_sdk/client.py +6 -2
- xenfra_sdk/constants.py +26 -0
- xenfra_sdk/db/session.py +8 -3
- xenfra_sdk/detection.py +262 -191
- xenfra_sdk/dockerizer.py +76 -120
- xenfra_sdk/engine.py +767 -172
- xenfra_sdk/events.py +254 -0
- xenfra_sdk/exceptions.py +9 -0
- xenfra_sdk/governance.py +150 -0
- xenfra_sdk/manifest.py +93 -138
- xenfra_sdk/mcp_client.py +7 -5
- xenfra_sdk/{models.py → models/__init__.py} +17 -1
- xenfra_sdk/models/context.py +61 -0
- xenfra_sdk/orchestrator.py +223 -99
- xenfra_sdk/privacy.py +11 -0
- xenfra_sdk/protocol.py +38 -0
- xenfra_sdk/railpack_adapter.py +357 -0
- xenfra_sdk/railpack_detector.py +587 -0
- xenfra_sdk/railpack_manager.py +312 -0
- xenfra_sdk/recipes.py +152 -19
- xenfra_sdk/resources/activity.py +45 -0
- xenfra_sdk/resources/build.py +157 -0
- xenfra_sdk/resources/deployments.py +22 -2
- xenfra_sdk/resources/intelligence.py +25 -0
- xenfra_sdk-0.2.7.dist-info/METADATA +118 -0
- xenfra_sdk-0.2.7.dist-info/RECORD +49 -0
- {xenfra_sdk-0.2.5.dist-info → xenfra_sdk-0.2.7.dist-info}/WHEEL +1 -1
- xenfra_sdk/templates/Caddyfile.j2 +0 -14
- xenfra_sdk/templates/Dockerfile.j2 +0 -41
- xenfra_sdk/templates/cloud-init.sh.j2 +0 -90
- xenfra_sdk/templates/docker-compose-multi.yml.j2 +0 -29
- xenfra_sdk/templates/docker-compose.yml.j2 +0 -30
- xenfra_sdk-0.2.5.dist-info/METADATA +0 -116
- xenfra_sdk-0.2.5.dist-info/RECORD +0 -38
xenfra_sdk/dockerizer.py
CHANGED
|
@@ -9,8 +9,6 @@ from pathlib import Path
|
|
|
9
9
|
from typing import Dict, Optional
|
|
10
10
|
import re
|
|
11
11
|
|
|
12
|
-
from jinja2 import Environment, FileSystemLoader
|
|
13
|
-
|
|
14
12
|
|
|
15
13
|
def detect_python_version(file_manifest: list = None) -> str:
|
|
16
14
|
"""
|
|
@@ -65,131 +63,89 @@ def detect_python_version(file_manifest: list = None) -> str:
|
|
|
65
63
|
return default_version
|
|
66
64
|
|
|
67
65
|
|
|
68
|
-
def
|
|
66
|
+
def detect_entrypoint(file_manifest: list = None, framework: str = "fastapi") -> str:
|
|
69
67
|
"""
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
Detect the correct uvicorn/gunicorn entrypoint from project structure.
|
|
69
|
+
|
|
70
|
+
Checks common patterns:
|
|
71
|
+
- main.py in root → main:app
|
|
72
|
+
- app.py in root → app:app
|
|
73
|
+
- src/main.py → src.main:app
|
|
74
|
+
- app/main.py → app.main:app
|
|
74
75
|
|
|
75
76
|
Args:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
- framework: str (fastapi, flask, django)
|
|
79
|
-
- port: int (default 8000)
|
|
80
|
-
Optional keys:
|
|
81
|
-
- command: str (start command, auto-generated if not provided)
|
|
82
|
-
- database: str (postgres, mysql, etc.)
|
|
83
|
-
- package_manager: str (pip, uv)
|
|
84
|
-
- dependency_file: str (requirements.txt, pyproject.toml)
|
|
85
|
-
- python_version: str (default python:3.11-slim)
|
|
77
|
+
file_manifest: List of file info dicts with 'path' key
|
|
78
|
+
framework: The framework being used (fastapi, flask, django)
|
|
86
79
|
|
|
87
80
|
Returns:
|
|
88
|
-
|
|
89
|
-
Returns empty dict if no framework is provided.
|
|
81
|
+
Entrypoint string (e.g., "main:app", "src.main:app")
|
|
90
82
|
"""
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
"
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
83
|
+
if not file_manifest:
|
|
84
|
+
return "main:app" # Default fallback
|
|
85
|
+
|
|
86
|
+
# Build set of paths for fast lookup
|
|
87
|
+
paths = {f.get('path', '') for f in file_manifest}
|
|
88
|
+
|
|
89
|
+
# Check common entrypoint patterns in priority order
|
|
90
|
+
entrypoint_patterns = [
|
|
91
|
+
("main.py", "main:app"),
|
|
92
|
+
("app.py", "app:app"),
|
|
93
|
+
("src/main.py", "src.main:app"),
|
|
94
|
+
("src/app.py", "src.app:app"),
|
|
95
|
+
("app/main.py", "app.main:app"),
|
|
96
|
+
("api/main.py", "api.main:app"),
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
for file_path, entrypoint in entrypoint_patterns:
|
|
100
|
+
if file_path in paths:
|
|
101
|
+
return entrypoint
|
|
102
|
+
|
|
103
|
+
# === PROTOCOL COMPLIANT: Framework Recipe Pattern ===
|
|
104
|
+
# Instead of if-else chains, use a dictionary of patterns
|
|
105
|
+
FRAMEWORK_PATTERNS = {
|
|
106
|
+
"django": {
|
|
107
|
+
"file_suffix": "wsgi.py",
|
|
108
|
+
"instance": "application",
|
|
109
|
+
"class_pattern": None, # Django uses wsgi discovery
|
|
110
|
+
},
|
|
111
|
+
"fastapi": {
|
|
112
|
+
"file_suffix": None,
|
|
113
|
+
"instance": "app",
|
|
114
|
+
"class_pattern": r'([a-zA-Z0-9_]+)\s*=\s*FastAPI\(',
|
|
115
|
+
},
|
|
116
|
+
"flask": {
|
|
117
|
+
"file_suffix": None,
|
|
118
|
+
"instance": "app",
|
|
119
|
+
"class_pattern": r'([a-zA-Z0-9_]+)\s*=\s*Flask\(',
|
|
120
|
+
},
|
|
128
121
|
}
|
|
129
122
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
123
|
+
# Django special case: Look for wsgi.py
|
|
124
|
+
recipe = FRAMEWORK_PATTERNS.get(framework, {})
|
|
125
|
+
if recipe.get("file_suffix"):
|
|
126
|
+
for path in paths:
|
|
127
|
+
if path.endswith(recipe["file_suffix"]):
|
|
128
|
+
module = path.replace("/", ".").replace(".py", "")
|
|
129
|
+
return f"{module}:{recipe['instance']}"
|
|
130
|
+
|
|
131
|
+
# === SEARCH & RESCUE: Deep Content Scan ===
|
|
132
|
+
# If filename scan fails, look inside .py files for instantiation
|
|
133
|
+
class_pattern = recipe.get("class_pattern")
|
|
134
|
+
if class_pattern:
|
|
135
|
+
for file_info in file_manifest:
|
|
136
|
+
path = file_info.get('path', '')
|
|
137
|
+
if not path.endswith(".py") or path.startswith("tests/"):
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
content = file_info.get('content', '')
|
|
141
|
+
if not content:
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
match = re.search(class_pattern, content)
|
|
145
|
+
if match:
|
|
146
|
+
instance = match.group(1)
|
|
147
|
+
module = path.replace("/", ".").replace(".py", "").replace("src.", "").replace("app.", "")
|
|
148
|
+
return f"{module}:{instance}"
|
|
154
149
|
|
|
155
|
-
|
|
156
|
-
if (project_root / "manage.py").is_file():
|
|
157
|
-
project_name = project_root.name
|
|
158
|
-
return "django", 8000, f"gunicorn {project_name}.wsgi:application --bind 0.0.0.0:8000"
|
|
150
|
+
return "main:app" # Default fallback
|
|
159
151
|
|
|
160
|
-
candidate_files = []
|
|
161
|
-
|
|
162
|
-
# Check directly in project root
|
|
163
|
-
for name in ["main.py", "app.py"]:
|
|
164
|
-
if (project_root / name).is_file():
|
|
165
|
-
candidate_files.append(project_root / name)
|
|
166
|
-
|
|
167
|
-
# Check in src/*/ (standard package layout)
|
|
168
|
-
for src_dir in project_root.glob("src/*"):
|
|
169
|
-
if src_dir.is_dir():
|
|
170
|
-
for name in ["main.py", "app.py"]:
|
|
171
|
-
if (src_dir / name).is_file():
|
|
172
|
-
candidate_files.append(src_dir / name)
|
|
173
|
-
|
|
174
|
-
import os
|
|
175
|
-
for file_path in candidate_files:
|
|
176
|
-
try:
|
|
177
|
-
with open(file_path, "r") as f:
|
|
178
|
-
content = f.read()
|
|
179
|
-
except Exception:
|
|
180
|
-
continue
|
|
181
|
-
|
|
182
|
-
try:
|
|
183
|
-
module_name = str(file_path.relative_to(project_root)).replace(os.sep, '.')[:-3]
|
|
184
|
-
if module_name.startswith("src."):
|
|
185
|
-
module_name = module_name[4:]
|
|
186
|
-
except ValueError:
|
|
187
|
-
module_name = file_path.stem
|
|
188
|
-
|
|
189
|
-
if "FastAPI" in content:
|
|
190
|
-
return "fastapi", 8000, f"uvicorn {module_name}:app --host 0.0.0.0 --port 8000"
|
|
191
|
-
|
|
192
|
-
if "Flask" in content:
|
|
193
|
-
return "flask", 5000, f"gunicorn {module_name}:app -b 0.0.0.0:5000"
|
|
194
|
-
|
|
195
|
-
return None, None, None
|