pam-python 0.2.6__tar.gz → 0.2.8__tar.gz
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.
- {pam_python-0.2.6 → pam_python-0.2.8}/PKG-INFO +4 -4
- {pam_python-0.2.6 → pam_python-0.2.8}/README.md +3 -3
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/__init__.py +1 -1
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/cli.py +29 -49
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/project/uv/Dockerfile +1 -1
- {pam_python-0.2.6 → pam_python-0.2.8}/pam_python.egg-info/PKG-INFO +4 -4
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_cli_templates.py +30 -7
- {pam_python-0.2.6 → pam_python-0.2.8}/LICENSE.txt +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/api.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/interface_task_manager.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/logger.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/models/__init__.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/models/data_input_v2.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/models/request_command.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/models/sqlite_download.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/__init__.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/area.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/axis.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/bar.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/box_plot.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/bubble.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/candlestick.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/collection.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/definition.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/donut.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/errors.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/fields.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/heatmap.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/line.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/manager.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/metric.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/pie.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/polar_area.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/radar.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/radial_bar.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/range_area.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/range_bar.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/range_transform.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/record.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/scatter.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/schema.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/store.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/table.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/transport.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/reports/treemap.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/request_file_format.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/result_batch_uploader.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/server.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/service.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/sqlite.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/task_manager.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/temp_file_utils.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/buildcmd/pamb +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/buildcmd/pamb-base.sh +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/init/AGENT.md +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/init/REPORTS.md +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/init/dockerignore.tmpl +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/init/gitignore.tmpl +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/init/main.tmpl +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/init/pylintrc.tmpl +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/init/run_unit_test.bat +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/init/run_unit_test.ps1 +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/init/run_unit_test.sh +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/project/pip/Dockerfile +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/project/pip/requirements.txt +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/project/uv/pyproject.toml +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/project/uv/python-version +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/service/common/service.yaml +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/service/pandas/functions.tmpl +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/service/pandas/service.test.tmpl +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/service/pandas/service_class.tmpl +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/service/polars/functions.tmpl +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/service/polars/service.test.tmpl +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/templates/service/polars/service_class.tmpl +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/tester_task.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam/utils.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam_python.egg-info/SOURCES.txt +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam_python.egg-info/dependency_links.txt +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam_python.egg-info/entry_points.txt +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam_python.egg-info/requires.txt +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pam_python.egg-info/top_level.txt +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/pyproject.toml +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/setup.cfg +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_api.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_data_error_v2.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_data_input_v2.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_data_timeout_v2.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_range_transform.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_report_store.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_request_data_v2.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_result_batch_uploader.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_service_dataframe_output.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_sqlite_download_v2.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_temp_file_utils.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_area_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_bar_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_box_plot_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_bubble_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_candlestick_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_donut_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_heatmap_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_line_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_metric_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_pie_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_polar_area_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_radar_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_radial_bar_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_range_area_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_range_bar_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_scatter_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_table_report.py +0 -0
- {pam_python-0.2.6 → pam_python-0.2.8}/tests/test_typed_treemap_report.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pam-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Pam Python Library
|
|
5
5
|
Author-email: Narongrit Kanhanoi <narongrit@pams.ai>
|
|
6
6
|
Project-URL: Homepage, https://github.com/heart/pam-python
|
|
@@ -70,9 +70,9 @@ uv add pam-python
|
|
|
70
70
|
uv run pam init
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
`pam init` recognizes a fresh uv project, keeps its uv configuration,
|
|
74
|
-
|
|
75
|
-
been edited.
|
|
73
|
+
`pam init` recognizes a fresh uv project, keeps its uv configuration, adds the
|
|
74
|
+
Flask and Gunicorn runtime dependencies, and adds the PAM scaffold. It refuses
|
|
75
|
+
projects whose generated source files have already been edited.
|
|
76
76
|
|
|
77
77
|
Pip remains supported for new projects that do not use uv:
|
|
78
78
|
|
|
@@ -50,9 +50,9 @@ uv add pam-python
|
|
|
50
50
|
uv run pam init
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
`pam init` recognizes a fresh uv project, keeps its uv configuration,
|
|
54
|
-
|
|
55
|
-
been edited.
|
|
53
|
+
`pam init` recognizes a fresh uv project, keeps its uv configuration, adds the
|
|
54
|
+
Flask and Gunicorn runtime dependencies, and adds the PAM scaffold. It refuses
|
|
55
|
+
projects whose generated source files have already been edited.
|
|
56
56
|
|
|
57
57
|
Pip remains supported for new projects that do not use uv:
|
|
58
58
|
|
|
@@ -59,6 +59,8 @@ ENGINE_DEPENDENCIES = {
|
|
|
59
59
|
"pandas": ["pandas>=2.2.3", "pyarrow>=19.0.1"],
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
UV_RUNTIME_DEPENDENCIES = ["Flask>=3.0.2", "gunicorn>=23.0.0"]
|
|
63
|
+
|
|
62
64
|
|
|
63
65
|
def main(argv=None):
|
|
64
66
|
args = list(sys.argv[1:] if argv is None else argv)
|
|
@@ -145,19 +147,10 @@ def _project_name(directory: Path) -> str:
|
|
|
145
147
|
|
|
146
148
|
|
|
147
149
|
def _assert_new_project(directory: Path):
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
conflicts = [
|
|
153
|
-
name for name in sorted(known_project_files)
|
|
154
|
-
if (directory / name).exists()
|
|
155
|
-
]
|
|
156
|
-
if conflicts:
|
|
157
|
-
raise FileExistsError(
|
|
158
|
-
"pam init only supports a new project directory; existing files: "
|
|
159
|
-
+ ", ".join(conflicts)
|
|
160
|
-
)
|
|
150
|
+
# Kept for backward compatibility; collision handling is now in init_project
|
|
151
|
+
# via _prompt_overwrite_if_needed. Do not enforce allow-list here so
|
|
152
|
+
# unrelated files like .DS_Store are ignored.
|
|
153
|
+
return
|
|
161
154
|
|
|
162
155
|
|
|
163
156
|
def _requirement_name(requirement: str) -> str:
|
|
@@ -180,45 +173,26 @@ def _assert_fresh_uv_project(directory: Path):
|
|
|
180
173
|
"Run `uv add pam-python` before `uv run pam init`"
|
|
181
174
|
)
|
|
182
175
|
|
|
183
|
-
python_version = directory / ".python-version"
|
|
184
|
-
if not python_version.is_file() or not python_version.read_text().strip().startswith("3.12"):
|
|
185
|
-
raise FileExistsError(
|
|
186
|
-
"A fresh uv project must pin Python 3.12; run `uv init --python 3.12`"
|
|
187
|
-
)
|
|
188
176
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
"
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
unexpected = sorted(path.name for path in directory.iterdir() if path.name not in allowed)
|
|
200
|
-
if unexpected:
|
|
177
|
+
def _prompt_overwrite_if_needed(directory: Path, templates: dict[str, str]) -> bool:
|
|
178
|
+
dest_names = set(templates.values()) | {PROJECT_MARKER, "__init__.py"}
|
|
179
|
+
conflicts = sorted(name for name in dest_names if (directory / name).exists())
|
|
180
|
+
if not conflicts:
|
|
181
|
+
return True
|
|
182
|
+
print("The following files will be overwritten:")
|
|
183
|
+
for name in conflicts:
|
|
184
|
+
print(f" - {name}")
|
|
185
|
+
# Single confirmation round as requested: list collisions once, ask once.
|
|
186
|
+
if not sys.stdin.isatty():
|
|
201
187
|
raise FileExistsError(
|
|
202
|
-
"pam init
|
|
203
|
-
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
readme = directory / "README.md"
|
|
207
|
-
if readme.is_file() and readme.read_text(encoding="utf-8").strip():
|
|
208
|
-
raise FileExistsError("pam init will not overwrite a non-empty README.md")
|
|
209
|
-
|
|
210
|
-
main_file = directory / "main.py"
|
|
211
|
-
if main_file.is_file():
|
|
212
|
-
source = main_file.read_text(encoding="utf-8")
|
|
213
|
-
uv_placeholder = re.fullmatch(
|
|
214
|
-
r'def main\(\):\n'
|
|
215
|
-
r' print\("Hello from [a-z0-9-]+!"\)\n\n\n'
|
|
216
|
-
r'if __name__ == "__main__":\n'
|
|
217
|
-
r' main\(\)\n?',
|
|
218
|
-
source,
|
|
188
|
+
"pam init will overwrite existing files: " + ", ".join(conflicts) + ". "
|
|
189
|
+
"Run in an interactive terminal and confirm to overwrite."
|
|
219
190
|
)
|
|
220
|
-
|
|
221
|
-
|
|
191
|
+
response = input("มีไฟล์ที่จะถูกเขียนทับตามรายการข้างบน ดำเนินการต่อหรือไม่? (y/N): ").strip().lower()
|
|
192
|
+
if response != "y":
|
|
193
|
+
print("Cancelled.")
|
|
194
|
+
return False
|
|
195
|
+
return True
|
|
222
196
|
|
|
223
197
|
|
|
224
198
|
def _run_uv(args: list[str], cwd: Path):
|
|
@@ -263,6 +237,12 @@ def init_project(package_manager: str | None = None):
|
|
|
263
237
|
if dest not in preserved_uv_files
|
|
264
238
|
}
|
|
265
239
|
|
|
240
|
+
if not _prompt_overwrite_if_needed(destination, templates):
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
if preinitialized_uv:
|
|
244
|
+
_run_uv(["add", *UV_RUNTIME_DEPENDENCIES], destination)
|
|
245
|
+
|
|
266
246
|
with tempfile.TemporaryDirectory(prefix=".pam-init-", dir=destination) as temp_dir:
|
|
267
247
|
staging = Path(temp_dir)
|
|
268
248
|
for src, dest in templates.items():
|
|
@@ -17,4 +17,4 @@ RUN mkdir -p "$DATA_FOLDER"
|
|
|
17
17
|
EXPOSE 8000
|
|
18
18
|
|
|
19
19
|
# Keep one worker: service lifecycle state is local to the process.
|
|
20
|
-
CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:8000", "main:app"]
|
|
20
|
+
CMD ["uv", "run", "--no-sync", "gunicorn", "-w", "1", "-b", "0.0.0.0:8000", "main:app"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pam-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Pam Python Library
|
|
5
5
|
Author-email: Narongrit Kanhanoi <narongrit@pams.ai>
|
|
6
6
|
Project-URL: Homepage, https://github.com/heart/pam-python
|
|
@@ -70,9 +70,9 @@ uv add pam-python
|
|
|
70
70
|
uv run pam init
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
`pam init` recognizes a fresh uv project, keeps its uv configuration,
|
|
74
|
-
|
|
75
|
-
been edited.
|
|
73
|
+
`pam init` recognizes a fresh uv project, keeps its uv configuration, adds the
|
|
74
|
+
Flask and Gunicorn runtime dependencies, and adds the PAM scaffold. It refuses
|
|
75
|
+
projects whose generated source files have already been edited.
|
|
76
76
|
|
|
77
77
|
Pip remains supported for new projects that do not use uv:
|
|
78
78
|
|
|
@@ -151,15 +151,23 @@ class CLITemplateTest(unittest.TestCase):
|
|
|
151
151
|
|
|
152
152
|
root = Path(temp_dir)
|
|
153
153
|
self.assertTrue((root / "pyproject.toml").is_file())
|
|
154
|
+
self.assertIn(
|
|
155
|
+
'"gunicorn>=23.0.0"',
|
|
156
|
+
(root / "pyproject.toml").read_text(),
|
|
157
|
+
)
|
|
154
158
|
self.assertTrue((root / "uv.lock").is_file())
|
|
155
159
|
self.assertFalse((root / "requirements.txt").exists())
|
|
156
160
|
self.assertFalse((root / "run_unit_test.sh").exists())
|
|
161
|
+
self.assertIn(
|
|
162
|
+
'CMD ["uv", "run", "--no-sync", "gunicorn"',
|
|
163
|
+
(root / "Dockerfile").read_text(),
|
|
164
|
+
)
|
|
157
165
|
self.assertEqual(
|
|
158
166
|
[["lock"], ["add", "polars>=1.43.0"]],
|
|
159
167
|
calls,
|
|
160
168
|
)
|
|
161
169
|
|
|
162
|
-
def
|
|
170
|
+
def test_init_adopts_a_fresh_uv_project_and_adds_runtime_dependencies(self):
|
|
163
171
|
calls = []
|
|
164
172
|
original_pyproject = """[project]
|
|
165
173
|
name = "customer-plugin"
|
|
@@ -186,6 +194,8 @@ if __name__ == "__main__":
|
|
|
186
194
|
|
|
187
195
|
with mock.patch.object(cli.shutil, "which", return_value="/usr/bin/uv"), mock.patch.object(
|
|
188
196
|
cli, "_run_uv", side_effect=lambda args, cwd: calls.append(list(args))
|
|
197
|
+
), mock.patch.object(cli.sys.stdin, "isatty", return_value=True), mock.patch(
|
|
198
|
+
"builtins.input", return_value="y"
|
|
189
199
|
):
|
|
190
200
|
cli.init_project()
|
|
191
201
|
|
|
@@ -194,8 +204,15 @@ if __name__ == "__main__":
|
|
|
194
204
|
self.assertEqual("version = 1\n", (root / "uv.lock").read_text())
|
|
195
205
|
self.assertEqual(".venv\n", (root / ".gitignore").read_text())
|
|
196
206
|
self.assertIn("plugin_server = Server", (root / "main.py").read_text())
|
|
207
|
+
self.assertIn(
|
|
208
|
+
'CMD ["uv", "run", "--no-sync", "gunicorn"',
|
|
209
|
+
(root / "Dockerfile").read_text(),
|
|
210
|
+
)
|
|
197
211
|
self.assertEqual("uv", json.loads((root / ".pam-project").read_text())["package_manager"])
|
|
198
|
-
self.assertEqual(
|
|
212
|
+
self.assertEqual(
|
|
213
|
+
[["add", "Flask>=3.0.2", "gunicorn>=23.0.0"]],
|
|
214
|
+
calls,
|
|
215
|
+
)
|
|
199
216
|
|
|
200
217
|
def test_init_adds_gitignore_when_fresh_uv_project_does_not_have_one(self):
|
|
201
218
|
uv_main = '''def main():
|
|
@@ -217,7 +234,11 @@ if __name__ == "__main__":
|
|
|
217
234
|
(root / "README.md").touch()
|
|
218
235
|
(root / "main.py").write_text(uv_main)
|
|
219
236
|
|
|
220
|
-
with mock.patch.object(cli.shutil, "which", return_value="/usr/bin/uv")
|
|
237
|
+
with mock.patch.object(cli.shutil, "which", return_value="/usr/bin/uv"), mock.patch.object(
|
|
238
|
+
cli, "_run_uv"
|
|
239
|
+
), mock.patch.object(
|
|
240
|
+
cli.sys.stdin, "isatty", return_value=True
|
|
241
|
+
), mock.patch("builtins.input", return_value="y"):
|
|
221
242
|
cli.init_project()
|
|
222
243
|
|
|
223
244
|
gitignore = (root / ".gitignore").read_text()
|
|
@@ -242,15 +263,17 @@ if __name__ == "__main__":
|
|
|
242
263
|
(root / ".python-version").write_text("3.12\n")
|
|
243
264
|
(root / "main.py").write_text("print('real code')\n")
|
|
244
265
|
|
|
245
|
-
with
|
|
246
|
-
|
|
266
|
+
with mock.patch.object(cli.sys.stdin, "isatty", return_value=False):
|
|
267
|
+
with self.assertRaisesRegex(FileExistsError, "will overwrite existing files: main.py"):
|
|
268
|
+
cli.init_project()
|
|
247
269
|
|
|
248
270
|
def test_init_rejects_an_existing_project_without_overwriting(self):
|
|
249
271
|
with tempfile.TemporaryDirectory() as temp_dir, WorkingDirectory(temp_dir):
|
|
250
272
|
original = Path("main.py")
|
|
251
273
|
original.write_text("keep me\n")
|
|
252
|
-
with
|
|
253
|
-
|
|
274
|
+
with mock.patch.object(cli.sys.stdin, "isatty", return_value=False):
|
|
275
|
+
with self.assertRaises(FileExistsError):
|
|
276
|
+
cli.init_project("pip")
|
|
254
277
|
self.assertEqual("keep me\n", original.read_text())
|
|
255
278
|
|
|
256
279
|
def test_service_name_validation_rejects_paths_and_hyphens(self):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|