shipit-cli 0.5.1__py3-none-any.whl → 0.5.2__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.
- shipit/cli.py +22 -9
- shipit/providers/mkdocs.py +12 -40
- shipit/providers/python.py +173 -90
- shipit/version.py +2 -2
- {shipit_cli-0.5.1.dist-info → shipit_cli-0.5.2.dist-info}/METADATA +1 -1
- {shipit_cli-0.5.1.dist-info → shipit_cli-0.5.2.dist-info}/RECORD +8 -8
- {shipit_cli-0.5.1.dist-info → shipit_cli-0.5.2.dist-info}/WHEEL +0 -0
- {shipit_cli-0.5.1.dist-info → shipit_cli-0.5.2.dist-info}/entry_points.txt +0 -0
shipit/cli.py
CHANGED
|
@@ -594,10 +594,10 @@ class LocalBuilder:
|
|
|
594
594
|
commands.append(step.command)
|
|
595
595
|
elif isinstance(step, WorkdirStep):
|
|
596
596
|
commands.append(f"cd {step.path}")
|
|
597
|
-
content = "#!/bin/bash\n{body}".format(
|
|
598
|
-
|
|
597
|
+
content = "#!/bin/bash\n{body}".format(body="\n".join(commands))
|
|
598
|
+
console.print(
|
|
599
|
+
f"\n[bold]Created prepare.sh script to run before packaging ✅[/bold]"
|
|
599
600
|
)
|
|
600
|
-
console.print(f"\n[bold]Created prepare.sh script to run before packaging ✅[/bold]")
|
|
601
601
|
manifest_panel = Panel(
|
|
602
602
|
Syntax(
|
|
603
603
|
content,
|
|
@@ -669,8 +669,8 @@ class WasmerBuilder:
|
|
|
669
669
|
mapper: Dict[str, MapperItem] = {
|
|
670
670
|
"python": {
|
|
671
671
|
"dependencies": {
|
|
672
|
-
"latest": "
|
|
673
|
-
"3.13": "
|
|
672
|
+
"latest": "python/python@=3.13.1",
|
|
673
|
+
"3.13": "python/python@=3.13.1",
|
|
674
674
|
},
|
|
675
675
|
"scripts": {"python"},
|
|
676
676
|
"aliases": {},
|
|
@@ -679,6 +679,13 @@ class WasmerBuilder:
|
|
|
679
679
|
"PYTHONHOME": "/cpython",
|
|
680
680
|
},
|
|
681
681
|
},
|
|
682
|
+
"pandoc": {
|
|
683
|
+
"dependencies": {
|
|
684
|
+
"latest": "wasmer/pandoc@=0.0.1",
|
|
685
|
+
"3.5": "wasmer/pandoc@=0.0.1",
|
|
686
|
+
},
|
|
687
|
+
"scripts": {"pandoc"},
|
|
688
|
+
},
|
|
682
689
|
"php": {
|
|
683
690
|
"dependencies": {
|
|
684
691
|
"latest": "php/php-32@=8.3.2104",
|
|
@@ -773,7 +780,9 @@ class WasmerBuilder:
|
|
|
773
780
|
|
|
774
781
|
body = "\n".join(filter(None, [env_lines, *commands]))
|
|
775
782
|
content = f"#!/bin/bash\n\n{body}"
|
|
776
|
-
console.print(
|
|
783
|
+
console.print(
|
|
784
|
+
f"\n[bold]Created prepare.sh script to run before packaging ✅[/bold]"
|
|
785
|
+
)
|
|
777
786
|
manifest_panel = Panel(
|
|
778
787
|
Syntax(
|
|
779
788
|
content,
|
|
@@ -840,12 +849,14 @@ class WasmerBuilder:
|
|
|
840
849
|
version
|
|
841
850
|
].split("@")
|
|
842
851
|
dependencies.add(package_name, version)
|
|
843
|
-
|
|
852
|
+
scripts = self.mapper[dep.name].get("scripts") or []
|
|
853
|
+
for script in scripts:
|
|
844
854
|
binaries[script] = {
|
|
845
855
|
"script": f"{package_name}:{script}",
|
|
846
856
|
"env": self.mapper[dep.name].get("env"),
|
|
847
857
|
}
|
|
848
|
-
|
|
858
|
+
aliases = self.mapper[dep.name].get("aliases") or {}
|
|
859
|
+
for alias, script in aliases.items():
|
|
849
860
|
binaries[alias] = {
|
|
850
861
|
"script": f"{package_name}:{script}",
|
|
851
862
|
"env": self.mapper[dep.name].get("env"),
|
|
@@ -922,7 +933,9 @@ class WasmerBuilder:
|
|
|
922
933
|
|
|
923
934
|
original_app_yaml_path = self.src_dir / "app.yaml"
|
|
924
935
|
if original_app_yaml_path.exists():
|
|
925
|
-
console.print(
|
|
936
|
+
console.print(
|
|
937
|
+
f"[bold]Using original app.yaml found in source directory[/bold]"
|
|
938
|
+
)
|
|
926
939
|
yaml_config = yaml.safe_load(original_app_yaml_path.read_text())
|
|
927
940
|
else:
|
|
928
941
|
yaml_config = {
|
shipit/providers/mkdocs.py
CHANGED
|
@@ -4,11 +4,15 @@ from pathlib import Path
|
|
|
4
4
|
from typing import Dict, Optional
|
|
5
5
|
|
|
6
6
|
from .base import DetectResult, DependencySpec, Provider, _exists, MountSpec
|
|
7
|
+
from .staticfile import StaticFileProvider
|
|
8
|
+
from .python import PythonProvider
|
|
7
9
|
|
|
8
10
|
|
|
9
|
-
class MkdocsProvider:
|
|
11
|
+
class MkdocsProvider(StaticFileProvider):
|
|
10
12
|
def __init__(self, path: Path):
|
|
11
13
|
self.path = path
|
|
14
|
+
self.python_provider = PythonProvider(path, only_build=True, extra_dependencies={"mkdocs"})
|
|
15
|
+
|
|
12
16
|
@classmethod
|
|
13
17
|
def name(cls) -> str:
|
|
14
18
|
return "mkdocs"
|
|
@@ -30,59 +34,27 @@ class MkdocsProvider:
|
|
|
30
34
|
|
|
31
35
|
def dependencies(self) -> list[DependencySpec]:
|
|
32
36
|
return [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
env_var="SHIPIT_PYTHON_VERSION",
|
|
36
|
-
default_version="3.13",
|
|
37
|
-
use_in_build=True,
|
|
38
|
-
),
|
|
39
|
-
DependencySpec(
|
|
40
|
-
"uv",
|
|
41
|
-
env_var="SHIPIT_UV_VERSION",
|
|
42
|
-
default_version="0.8.15",
|
|
43
|
-
use_in_build=True,
|
|
44
|
-
),
|
|
45
|
-
DependencySpec(
|
|
46
|
-
"static-web-server",
|
|
47
|
-
env_var="SHIPIT_SWS_VERSION",
|
|
48
|
-
default_version="2.38.0",
|
|
49
|
-
use_in_serve=True,
|
|
50
|
-
),
|
|
37
|
+
*self.python_provider.dependencies(),
|
|
38
|
+
*super().dependencies(),
|
|
51
39
|
]
|
|
52
40
|
|
|
53
41
|
def declarations(self) -> Optional[str]:
|
|
54
|
-
return
|
|
42
|
+
return "mkdocs_version = getenv(\"SHIPIT_MKDOCS_VERSION\") or \"1.6.1\"\n" + (self.python_provider.declarations() or "")
|
|
55
43
|
|
|
56
44
|
def build_steps(self) -> list[str]:
|
|
57
|
-
has_requirements = _exists(self.path, "requirements.txt")
|
|
58
|
-
if has_requirements:
|
|
59
|
-
install_lines = [
|
|
60
|
-
"run(\"uv init --no-managed-python\", inputs=[], outputs=[\".\"], group=\"install\")",
|
|
61
|
-
"run(f\"uv add -r requirements.txt\", inputs=[\"requirements.txt\"], outputs=[\".venv\"], group=\"install\")",
|
|
62
|
-
]
|
|
63
|
-
else:
|
|
64
|
-
install_lines = [
|
|
65
|
-
"mkdocs_version = getenv(\"SHIPIT_MKDOCS_VERSION\") or \"1.6.1\"",
|
|
66
|
-
"run(\"uv init --no-managed-python\", inputs=[], outputs=[\".venv\"], group=\"install\")",
|
|
67
|
-
"run(f\"uv add mkdocs=={mkdocs_version}\", group=\"install\")",
|
|
68
|
-
]
|
|
69
45
|
return [
|
|
70
|
-
*
|
|
71
|
-
"copy(\".\", \".\", ignore=[\".venv\", \".git\", \"__pycache__\"])",
|
|
46
|
+
*self.python_provider.build_steps(),
|
|
72
47
|
"run(\"uv run mkdocs build --site-dir={}\".format(app[\"build\"]), outputs=[\".\"], group=\"build\")",
|
|
73
48
|
]
|
|
74
49
|
|
|
75
50
|
def prepare_steps(self) -> Optional[list[str]]:
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
def commands(self) -> Dict[str, str]:
|
|
79
|
-
return {"start": '"static-web-server --root /app"'}
|
|
51
|
+
return self.python_provider.prepare_steps()
|
|
80
52
|
|
|
81
53
|
def assets(self) -> Optional[Dict[str, str]]:
|
|
82
54
|
return None
|
|
83
55
|
|
|
84
56
|
def mounts(self) -> list[MountSpec]:
|
|
85
|
-
return [MountSpec("app")]
|
|
57
|
+
return [MountSpec("app"), *self.python_provider.mounts()]
|
|
86
58
|
|
|
87
59
|
def env(self) -> Optional[Dict[str, str]]:
|
|
88
|
-
return
|
|
60
|
+
return self.python_provider.env()
|
shipit/providers/python.py
CHANGED
|
@@ -29,6 +29,7 @@ class PythonServer(Enum):
|
|
|
29
29
|
# Gunicorn = "gunicorn"
|
|
30
30
|
Daphne = "daphne"
|
|
31
31
|
|
|
32
|
+
|
|
32
33
|
class DatabaseType(Enum):
|
|
33
34
|
MySQL = "mysql"
|
|
34
35
|
PostgreSQL = "postgresql"
|
|
@@ -41,8 +42,17 @@ class PythonProvider:
|
|
|
41
42
|
extra_dependencies: Set[str]
|
|
42
43
|
asgi_application: Optional[str] = None
|
|
43
44
|
wsgi_application: Optional[str] = None
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
uses_ffmpeg: bool = False
|
|
46
|
+
uses_pandoc: bool = False
|
|
47
|
+
only_build: bool = False
|
|
48
|
+
install_requires_all_files: bool = False
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
path: Path,
|
|
53
|
+
only_build: bool = False,
|
|
54
|
+
extra_dependencies: Optional[Set[str]] = None,
|
|
55
|
+
):
|
|
46
56
|
self.path = path
|
|
47
57
|
if _exists(self.path, ".python-version"):
|
|
48
58
|
python_version = (self.path / ".python-version").read_text().strip()
|
|
@@ -50,6 +60,11 @@ class PythonProvider:
|
|
|
50
60
|
python_version = "3.13"
|
|
51
61
|
self.default_python_version = python_version
|
|
52
62
|
self.extra_dependencies = set()
|
|
63
|
+
self.only_build = only_build
|
|
64
|
+
self.extra_dependencies = extra_dependencies or set()
|
|
65
|
+
|
|
66
|
+
if self.only_build:
|
|
67
|
+
return
|
|
53
68
|
|
|
54
69
|
pg_deps = {
|
|
55
70
|
"asyncpg",
|
|
@@ -57,9 +72,11 @@ class PythonProvider:
|
|
|
57
72
|
"psycopg",
|
|
58
73
|
"psycopg2",
|
|
59
74
|
"psycopg-binary",
|
|
60
|
-
"psycopg2-binary"
|
|
75
|
+
"psycopg2-binary",
|
|
76
|
+
}
|
|
61
77
|
mysql_deps = {"mysqlclient", "pymysql", "mysql-connector-python", "aiomysql"}
|
|
62
78
|
found_deps = self.check_deps(
|
|
79
|
+
"file://", # This is not really a dependency, but as a way to check if the install script requires all files
|
|
63
80
|
"streamlit",
|
|
64
81
|
"django",
|
|
65
82
|
"mcp",
|
|
@@ -69,11 +86,17 @@ class PythonProvider:
|
|
|
69
86
|
"daphne",
|
|
70
87
|
"hypercorn",
|
|
71
88
|
"uvicorn",
|
|
89
|
+
# Other
|
|
90
|
+
"ffmpeg",
|
|
91
|
+
"pandoc",
|
|
72
92
|
# "gunicorn",
|
|
73
93
|
*mysql_deps,
|
|
74
94
|
*pg_deps,
|
|
75
95
|
)
|
|
76
96
|
|
|
97
|
+
if "file://" in found_deps:
|
|
98
|
+
self.install_requires_all_files = True
|
|
99
|
+
|
|
77
100
|
# ASGI/WSGI Server
|
|
78
101
|
if "uvicorn" in found_deps:
|
|
79
102
|
server = PythonServer.Uvicorn
|
|
@@ -87,17 +110,30 @@ class PythonProvider:
|
|
|
87
110
|
server = None
|
|
88
111
|
self.server = server
|
|
89
112
|
|
|
113
|
+
if "ffmpeg" in found_deps:
|
|
114
|
+
self.uses_ffmpeg = True
|
|
115
|
+
if "pandoc" in found_deps:
|
|
116
|
+
self.uses_pandoc = True
|
|
117
|
+
|
|
90
118
|
# Set framework
|
|
91
119
|
if _exists(self.path, "manage.py") and ("django" in found_deps):
|
|
92
120
|
framework = PythonFramework.Django
|
|
93
121
|
# Find the settings.py file using glob
|
|
94
|
-
|
|
122
|
+
try:
|
|
123
|
+
settings_file = next(self.path.glob("**/settings.py"))
|
|
124
|
+
except StopIteration:
|
|
125
|
+
settings_file = None
|
|
95
126
|
if settings_file:
|
|
96
|
-
asgi_match = re.search(
|
|
127
|
+
asgi_match = re.search(
|
|
128
|
+
r"ASGI_APPLICATION\s*=\s*['\"](.*)['\"]", settings_file.read_text()
|
|
129
|
+
)
|
|
97
130
|
if asgi_match:
|
|
98
131
|
self.asgi_application = asgi_match.group(1)
|
|
99
132
|
else:
|
|
100
|
-
wsgi_match = re.search(
|
|
133
|
+
wsgi_match = re.search(
|
|
134
|
+
r"WSGI_APPLICATION\s*=\s*['\"](.*)['\"]",
|
|
135
|
+
settings_file.read_text(),
|
|
136
|
+
)
|
|
101
137
|
if wsgi_match:
|
|
102
138
|
self.wsgi_application = wsgi_match.group(1)
|
|
103
139
|
|
|
@@ -154,7 +190,7 @@ class PythonProvider:
|
|
|
154
190
|
break
|
|
155
191
|
if not deps:
|
|
156
192
|
break
|
|
157
|
-
return initial_deps-deps
|
|
193
|
+
return initial_deps - deps
|
|
158
194
|
|
|
159
195
|
@classmethod
|
|
160
196
|
def name(cls) -> str:
|
|
@@ -178,7 +214,7 @@ class PythonProvider:
|
|
|
178
214
|
return "python"
|
|
179
215
|
|
|
180
216
|
def dependencies(self) -> list[DependencySpec]:
|
|
181
|
-
|
|
217
|
+
deps = [
|
|
182
218
|
DependencySpec(
|
|
183
219
|
"python",
|
|
184
220
|
env_var="SHIPIT_PYTHON_VERSION",
|
|
@@ -193,73 +229,131 @@ class PythonProvider:
|
|
|
193
229
|
use_in_build=True,
|
|
194
230
|
),
|
|
195
231
|
]
|
|
232
|
+
if self.uses_pandoc:
|
|
233
|
+
deps.append(
|
|
234
|
+
DependencySpec(
|
|
235
|
+
"pandoc",
|
|
236
|
+
env_var="SHIPIT_PANDOC_VERSION",
|
|
237
|
+
use_in_build=False,
|
|
238
|
+
use_in_serve=True,
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
return deps
|
|
196
242
|
|
|
197
243
|
def declarations(self) -> Optional[str]:
|
|
244
|
+
if self.only_build:
|
|
245
|
+
return (
|
|
246
|
+
'cross_platform = getenv("SHIPIT_PYTHON_CROSS_PLATFORM")\n'
|
|
247
|
+
"venv = local_venv\n"
|
|
248
|
+
)
|
|
198
249
|
return (
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
250
|
+
'cross_platform = getenv("SHIPIT_PYTHON_CROSS_PLATFORM")\n'
|
|
251
|
+
'python_extra_index_url = getenv("SHIPIT_PYTHON_EXTRA_INDEX_URL")\n'
|
|
252
|
+
'precompile_python = getenv("SHIPIT_PYTHON_PRECOMPILE") in ["true", "True", "TRUE", "1", "on", "yes", "y", "Y", "YES", "On", "ON"]\n'
|
|
253
|
+
'python_cross_packages_path = venv["build"] + f"/lib/python{python_version}/site-packages"\n'
|
|
254
|
+
'python_serve_path = "{}/lib/python{}/site-packages".format(venv["serve"], python_version)\n'
|
|
204
255
|
)
|
|
205
256
|
|
|
206
257
|
def build_steps(self) -> list[str]:
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
258
|
+
if not self.only_build:
|
|
259
|
+
steps = ['workdir(app["build"])']
|
|
260
|
+
else:
|
|
261
|
+
steps = []
|
|
210
262
|
|
|
211
263
|
extra_deps = ", ".join([f"{dep}" for dep in self.extra_dependencies])
|
|
264
|
+
has_requirements = _exists(self.path, "requirements.txt")
|
|
212
265
|
if _exists(self.path, "pyproject.toml"):
|
|
213
266
|
input_files = ["pyproject.toml"]
|
|
214
267
|
extra_args = ""
|
|
215
268
|
if _exists(self.path, "uv.lock"):
|
|
216
269
|
input_files.append("uv.lock")
|
|
217
270
|
extra_args = " --locked"
|
|
218
|
-
inputs = ", ".join([f"
|
|
271
|
+
inputs = ", ".join([f'"{input}"' for input in input_files])
|
|
219
272
|
steps += [
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
273
|
+
'env(UV_PROJECT_ENVIRONMENT=local_venv["build"] if cross_platform else venv["build"])',
|
|
274
|
+
'copy(".", ".")' if self.install_requires_all_files else None,
|
|
275
|
+
f'run(f"uv sync --compile --python python{{python_version}} --no-managed-python{extra_args}", inputs=[{inputs}], group="install")',
|
|
276
|
+
'copy("pyproject.toml", "pyproject.toml")'
|
|
277
|
+
if not self.install_requires_all_files
|
|
278
|
+
else None,
|
|
279
|
+
f'run("uv add {extra_deps}", group="install")' if extra_deps else None,
|
|
227
280
|
]
|
|
228
|
-
|
|
281
|
+
if not self.only_build:
|
|
282
|
+
steps += [
|
|
283
|
+
'run(f"uv pip compile pyproject.toml --python-version={python_version} --universal --extra-index-url {python_extra_index_url} --index-url=https://pypi.org/simple --emit-index-url --only-binary :all: -o cross-requirements.txt", outputs=["cross-requirements.txt"]) if cross_platform else None',
|
|
284
|
+
f'run(f"uvx pip install -r cross-requirements.txt {extra_deps} --target {{python_cross_packages_path}} --platform {{cross_platform}} --only-binary=:all: --python-version={{python_version}} --compile") if cross_platform else None',
|
|
285
|
+
'run("rm cross-requirements.txt") if cross_platform else None',
|
|
286
|
+
]
|
|
287
|
+
elif has_requirements or extra_deps:
|
|
229
288
|
steps += [
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
"run(f\"uv pip compile requirements.txt --python-version={python_version} --universal --extra-index-url {python_extra_index_url} --index-url=https://pypi.org/simple --emit-index-url --only-binary :all: -o cross-requirements.txt\", inputs=[\"requirements.txt\"], outputs=[\"cross-requirements.txt\"]) if cross_platform else None",
|
|
234
|
-
f"run(f\"uvx pip install -r cross-requirements.txt {extra_deps} --target {{python_cross_packages_path}} --platform {{cross_platform}} --only-binary=:all: --python-version={{python_version}} --compile\") if cross_platform else None",
|
|
235
|
-
"run(\"rm cross-requirements.txt\") if cross_platform else None",
|
|
289
|
+
'env(UV_PROJECT_ENVIRONMENT=local_venv["build"] if cross_platform else venv["build"])',
|
|
290
|
+
'run(f"uv init --no-workspace --no-managed-python --python python{python_version}", inputs=[], outputs=["uv.lock"], group="install")',
|
|
291
|
+
'copy(".", ".")' if self.install_requires_all_files else None,
|
|
236
292
|
]
|
|
293
|
+
if has_requirements:
|
|
294
|
+
steps += [
|
|
295
|
+
f'run("uv add -r requirements.txt {extra_deps}", inputs=["requirements.txt"], group="install")',
|
|
296
|
+
]
|
|
297
|
+
else:
|
|
298
|
+
steps += [
|
|
299
|
+
f'run("uv add {extra_deps}", group="install")',
|
|
300
|
+
]
|
|
301
|
+
if not self.only_build:
|
|
302
|
+
steps += [
|
|
303
|
+
'run(f"uv pip compile requirements.txt --python-version={python_version} --universal --extra-index-url {python_extra_index_url} --index-url=https://pypi.org/simple --emit-index-url --only-binary :all: -o cross-requirements.txt", inputs=["requirements.txt"], outputs=["cross-requirements.txt"]) if cross_platform else None',
|
|
304
|
+
f'run(f"uvx pip install -r cross-requirements.txt {extra_deps} --target {{python_cross_packages_path}} --platform {{cross_platform}} --only-binary=:all: --python-version={{python_version}} --compile") if cross_platform else None',
|
|
305
|
+
'run("rm cross-requirements.txt") if cross_platform else None',
|
|
306
|
+
]
|
|
237
307
|
|
|
238
308
|
steps += [
|
|
239
|
-
|
|
240
|
-
|
|
309
|
+
'path((local_venv["build"] if cross_platform else venv["build"]) + "/bin")',
|
|
310
|
+
'copy(".", ".", ignore=[".venv", ".git", "__pycache__"])',
|
|
241
311
|
]
|
|
242
312
|
if self.framework == PythonFramework.MCP:
|
|
243
313
|
steps += [
|
|
244
|
-
|
|
245
|
-
|
|
314
|
+
'run("mkdir -p {}/bin".format(venv["build"])) if cross_platform else None',
|
|
315
|
+
'run("cp {}/bin/mcp {}/bin/mcp".format(local_venv["build"], venv["build"])) if cross_platform else None',
|
|
246
316
|
]
|
|
247
317
|
return list(filter(None, steps))
|
|
248
318
|
|
|
249
319
|
def prepare_steps(self) -> Optional[list[str]]:
|
|
320
|
+
if self.only_build:
|
|
321
|
+
return []
|
|
250
322
|
return [
|
|
251
|
-
'run("echo
|
|
323
|
+
'run("echo \\"Precompiling Python code...\\"") if precompile_python else None',
|
|
252
324
|
'run(f"python -m compileall -o 2 {python_serve_path}") if precompile_python else None',
|
|
253
|
-
'run("echo
|
|
325
|
+
'run("echo \\"Precompiling package code...\\"") if precompile_python else None',
|
|
254
326
|
'run("python -m compileall -o 2 {}".format(app["serve"])) if precompile_python else None',
|
|
255
327
|
]
|
|
256
328
|
|
|
329
|
+
def get_main_file(self) -> Optional[str]:
|
|
330
|
+
paths_to_try = ["main.py", "app.py", "streamlit_app.py", "Home.py", "*_app.py"]
|
|
331
|
+
for path in paths_to_try:
|
|
332
|
+
if "*" in path:
|
|
333
|
+
continue # This is for the glob finder
|
|
334
|
+
if _exists(self.path, path):
|
|
335
|
+
return path
|
|
336
|
+
if _exists(self.path, f"src/{path}"):
|
|
337
|
+
return f"src/{path}"
|
|
338
|
+
for path in paths_to_try:
|
|
339
|
+
try:
|
|
340
|
+
found_path = next(self.path.glob(f"**/{path}.py"))
|
|
341
|
+
except StopIteration:
|
|
342
|
+
found_path = None
|
|
343
|
+
if found_path:
|
|
344
|
+
return found_path.relative_to(self.path)
|
|
345
|
+
return None
|
|
346
|
+
|
|
257
347
|
def commands(self) -> Dict[str, str]:
|
|
348
|
+
if self.only_build:
|
|
349
|
+
return {}
|
|
258
350
|
if self.framework == PythonFramework.Django:
|
|
259
351
|
start_cmd = None
|
|
260
352
|
if self.server == PythonServer.Daphne and self.asgi_application:
|
|
261
353
|
asgi_application = format_app_import(self.asgi_application)
|
|
262
|
-
start_cmd =
|
|
354
|
+
start_cmd = (
|
|
355
|
+
f'"python -m daphne {asgi_application} --bind 0.0.0.0 --port 8000"'
|
|
356
|
+
)
|
|
263
357
|
elif self.server == PythonServer.Uvicorn:
|
|
264
358
|
if self.asgi_application:
|
|
265
359
|
asgi_application = format_app_import(self.asgi_application)
|
|
@@ -274,73 +368,58 @@ class PythonProvider:
|
|
|
274
368
|
start_cmd = '"python manage.py runserver 0.0.0.0:8000"'
|
|
275
369
|
migrate_cmd = '"python manage.py migrate"'
|
|
276
370
|
return {"start": start_cmd, "after_deploy": migrate_cmd}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
371
|
+
|
|
372
|
+
main_file = self.get_main_file()
|
|
373
|
+
|
|
374
|
+
if not main_file:
|
|
375
|
+
start_cmd = '"python -c \'print(\\"No start command detected, please provide a start command manually\\")\'"'
|
|
376
|
+
return {"start": start_cmd}
|
|
377
|
+
|
|
378
|
+
if self.framework == PythonFramework.FastAPI:
|
|
379
|
+
python_path = file_to_python_path(main_file)
|
|
380
|
+
path = f"{python_path}:app"
|
|
283
381
|
if self.server == PythonServer.Uvicorn:
|
|
284
382
|
start_cmd = f'"python -m uvicorn {path} --host 0.0.0.0 --port 8000"'
|
|
285
383
|
elif self.server == PythonServer.Hypercorn:
|
|
286
384
|
start_cmd = f'"python -m hypercorn {path} --bind 0.0.0.0:8000"'
|
|
287
385
|
else:
|
|
288
|
-
start_cmd = '"python -c \'print(
|
|
386
|
+
start_cmd = '"python -c \'print(\\"No start command detected, please provide a start command manually\\")\'"'
|
|
289
387
|
return {"start": start_cmd}
|
|
388
|
+
|
|
290
389
|
elif self.framework == PythonFramework.Streamlit:
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
elif _exists(self.path, "src/main.py"):
|
|
294
|
-
path = "src/main.py"
|
|
295
|
-
if _exists(self.path, "app.py"):
|
|
296
|
-
path = "app.py"
|
|
297
|
-
elif _exists(self.path, "src/app.py"):
|
|
298
|
-
path = "src/app.py"
|
|
299
|
-
elif _exists(self.path, "src/streamlit_app.py"):
|
|
300
|
-
path = "src/streamlit_app.py"
|
|
301
|
-
elif _exists(self.path, "streamlit_app.py"):
|
|
302
|
-
path = "streamlit_app.py"
|
|
303
|
-
elif _exists(self.path, "Home.py"):
|
|
304
|
-
path = "Home.py"
|
|
305
|
-
|
|
306
|
-
start_cmd = f'"python -m streamlit run {path} --server.port 8000 --server.address 0.0.0.0 --server.headless true"'
|
|
390
|
+
start_cmd = f'"python -m streamlit run {main_file} --server.port 8000 --server.address 0.0.0.0 --server.headless true"'
|
|
391
|
+
|
|
307
392
|
elif self.framework == PythonFramework.Flask:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if _exists(self.path, "app.py"):
|
|
311
|
-
path = "app:app"
|
|
312
|
-
elif _exists(self.path, "src/main.py"):
|
|
313
|
-
path = "src.main:app"
|
|
393
|
+
python_path = file_to_python_path(main_file)
|
|
394
|
+
path = f"{python_path}:app"
|
|
314
395
|
# start_cmd = f'"python -m flask --app {path} run --debug --host 0.0.0.0 --port 8000"'
|
|
315
396
|
start_cmd = f'"python -m uvicorn {path} --interface=wsgi --host 0.0.0.0 --port 8000"'
|
|
397
|
+
|
|
316
398
|
elif self.framework == PythonFramework.MCP:
|
|
317
|
-
|
|
318
|
-
|
|
399
|
+
contents = (self.path / main_file).read_text()
|
|
400
|
+
if 'if __name__ == "__main__"' in contents or "mcp.run" in contents:
|
|
401
|
+
start_cmd = f'"python {main_file}"'
|
|
319
402
|
else:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
path = path.relative_to(self.path)
|
|
323
|
-
else:
|
|
324
|
-
path = "main.py"
|
|
325
|
-
start_cmd = f'"python {{}}/bin/mcp run {path} --transport=streamable-http".format(venv[\"serve\"])'
|
|
403
|
+
start_cmd = f'"python {{}}/bin/mcp run {main_file} --transport=streamable-http".format(venv["serve"])'
|
|
404
|
+
|
|
326
405
|
elif self.framework == PythonFramework.FastHTML:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
elif _exists(self.path, "src/main.py"):
|
|
330
|
-
path = "src.main:app"
|
|
406
|
+
python_path = file_to_python_path(main_file)
|
|
407
|
+
path = f"{python_path}:app"
|
|
331
408
|
start_cmd = f'"python -m uvicorn {path} --host 0.0.0.0 --port 8000"'
|
|
332
|
-
|
|
333
|
-
start_cmd = '"python main.py"'
|
|
334
|
-
elif _exists(self.path, "src/main.py"):
|
|
335
|
-
start_cmd = '"python src/main.py"'
|
|
409
|
+
|
|
336
410
|
else:
|
|
337
|
-
start_cmd = '"python
|
|
411
|
+
start_cmd = f'"python {main_file}"'
|
|
412
|
+
|
|
338
413
|
return {"start": start_cmd}
|
|
339
414
|
|
|
340
415
|
def assets(self) -> Optional[Dict[str, str]]:
|
|
341
416
|
return None
|
|
342
417
|
|
|
343
418
|
def mounts(self) -> list[MountSpec]:
|
|
419
|
+
if self.only_build:
|
|
420
|
+
return [
|
|
421
|
+
MountSpec("local_venv", attach_to_serve=False),
|
|
422
|
+
]
|
|
344
423
|
return [
|
|
345
424
|
MountSpec("app"),
|
|
346
425
|
MountSpec("venv"),
|
|
@@ -348,19 +427,23 @@ class PythonProvider:
|
|
|
348
427
|
]
|
|
349
428
|
|
|
350
429
|
def env(self) -> Optional[Dict[str, str]]:
|
|
430
|
+
if self.only_build:
|
|
431
|
+
return {}
|
|
351
432
|
# For Django projects, generate an empty env dict to surface the field
|
|
352
433
|
# in the Shipit file. Other Python projects omit it by default.
|
|
353
|
-
env_vars = {
|
|
354
|
-
"PYTHONPATH": "python_serve_path",
|
|
355
|
-
"HOME": "app[\"serve\"]"
|
|
356
|
-
}
|
|
434
|
+
env_vars = {"PYTHONPATH": "python_serve_path", "HOME": 'app["serve"]'}
|
|
357
435
|
if self.framework == PythonFramework.Streamlit:
|
|
358
436
|
env_vars["STREAMLIT_SERVER_HEADLESS"] = '"true"'
|
|
359
437
|
elif self.framework == PythonFramework.MCP:
|
|
360
|
-
env_vars["FASTMCP_HOST"] = "
|
|
361
|
-
env_vars["FASTMCP_PORT"] = "
|
|
438
|
+
env_vars["FASTMCP_HOST"] = '"0.0.0.0"'
|
|
439
|
+
env_vars["FASTMCP_PORT"] = '"8000"'
|
|
362
440
|
return env_vars
|
|
363
441
|
|
|
442
|
+
|
|
364
443
|
def format_app_import(asgi_application: str) -> str:
|
|
365
444
|
# Transform "mysite.asgi.application" to "mysite.asgi:application" using regex
|
|
366
445
|
return re.sub(r"\.([^.]+)$", r":\1", asgi_application)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def file_to_python_path(path: str) -> str:
|
|
449
|
+
return path.rstrip(".py").replace("/", ".").replace("\\", ".")
|
shipit/version.py
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
shipit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
shipit/cli.py,sha256=
|
|
2
|
+
shipit/cli.py,sha256=ATxOyybYHHVIk7gnS87mdla3TfrltCJQ7JWDW8rIITI,50191
|
|
3
3
|
shipit/generator.py,sha256=4pezEo4OzpDFSFqiFpGCpdwo72VkItS490R_f2rIz2k,5755
|
|
4
|
-
shipit/version.py,sha256=
|
|
4
|
+
shipit/version.py,sha256=SmR39rp7Tr-Uq9n5VRmIaRAJz1IADkiSlE0rZaYtDIQ,95
|
|
5
5
|
shipit/assets/php/php.ini,sha256=f4irndAjB4GuuouEImRkNV22Q-yw1KqR-43jAMDw730,2531
|
|
6
6
|
shipit/providers/base.py,sha256=a_5VA1tV4_QbH83yjPCTHsNR23EJT2CiKUpWA_pu_lo,2373
|
|
7
7
|
shipit/providers/gatsby.py,sha256=uwNjIJloS9JwKXqkbhihgdTTpJL4iL4bLCZ5kuzqqNs,2138
|
|
8
8
|
shipit/providers/hugo.py,sha256=tbn_1t_1AwXHk3-J6mGA2C0D7-3Wpzr-fKpDNhqHy2Q,1439
|
|
9
9
|
shipit/providers/laravel.py,sha256=rDpfx7RyF4sK0xxDAWefX0IiguU2xdgEXP2jJp1Jdzo,2777
|
|
10
|
-
shipit/providers/mkdocs.py,sha256=
|
|
10
|
+
shipit/providers/mkdocs.py,sha256=GgcoHLvbShhLUOQle6hIdp9x1SckS4r64P-LAoJtB6A,1850
|
|
11
11
|
shipit/providers/node_static.py,sha256=K55BXkNz4QXSXR2wdg5diP6HLpmksC70ph16zox7v6Y,2309
|
|
12
12
|
shipit/providers/php.py,sha256=Hmtv47K5qtYbQ3v9SSk-_KTNlhXedStg2MxhTTOK9ac,2594
|
|
13
|
-
shipit/providers/python.py,sha256=
|
|
13
|
+
shipit/providers/python.py,sha256=mlDeGGlOnme3MBdmH02KovAxxRHhBHUVQOd2fJWWtfs,18350
|
|
14
14
|
shipit/providers/registry.py,sha256=UisII1dr24ZxmDD8GnpTsyNwPN9W8MnAHQ1Px1iJ-OQ,661
|
|
15
15
|
shipit/providers/staticfile.py,sha256=Y4oqw6dNDU2crzcWQ5SEgnXHoDy0CXRntABwlgdf1mo,1827
|
|
16
|
-
shipit_cli-0.5.
|
|
17
|
-
shipit_cli-0.5.
|
|
18
|
-
shipit_cli-0.5.
|
|
19
|
-
shipit_cli-0.5.
|
|
16
|
+
shipit_cli-0.5.2.dist-info/METADATA,sha256=msW1GfoUrqm4HsI5P1Y8CQB1lY4n87oXXI7xHz07ZrU,462
|
|
17
|
+
shipit_cli-0.5.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
18
|
+
shipit_cli-0.5.2.dist-info/entry_points.txt,sha256=7AE1NjSrHaSDfbfsRRO50KKnHFTbB0Imsccd1WynzAQ,72
|
|
19
|
+
shipit_cli-0.5.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|