pam-python 0.2.1__tar.gz → 0.2.2__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.1/pam_python.egg-info → pam_python-0.2.2}/PKG-INFO +17 -21
- {pam_python-0.2.1 → pam_python-0.2.2}/README.md +16 -20
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/__init__.py +1 -1
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/cli.py +79 -4
- {pam_python-0.2.1 → pam_python-0.2.2/pam_python.egg-info}/PKG-INFO +17 -21
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_cli_templates.py +52 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/LICENSE.txt +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/api.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/interface_task_manager.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/logger.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/models/__init__.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/models/request_command.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/models/sqlite_download.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/__init__.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/area.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/axis.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/bar.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/box_plot.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/bubble.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/candlestick.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/collection.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/definition.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/donut.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/errors.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/fields.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/heatmap.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/line.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/manager.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/metric.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/pie.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/polar_area.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/radar.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/radial_bar.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/range_area.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/range_bar.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/range_transform.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/record.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/scatter.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/schema.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/store.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/table.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/transport.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/reports/treemap.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/request_file_format.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/result_batch_uploader.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/server.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/service.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/sqlite.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/task_manager.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/temp_file_utils.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/buildcmd/pamb +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/buildcmd/pamb-base.sh +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/init/AGENT.md +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/init/REPORTS.md +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/init/dockerignore.tmpl +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/init/gitignore.tmpl +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/init/main.tmpl +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/init/pylintrc.tmpl +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/init/run_unit_test.bat +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/init/run_unit_test.ps1 +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/init/run_unit_test.sh +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/project/pip/Dockerfile +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/project/pip/requirements.txt +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/project/uv/Dockerfile +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/project/uv/pyproject.toml +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/project/uv/python-version +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/service/common/service.yaml +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/service/pandas/functions.tmpl +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/service/pandas/service.test.tmpl +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/service/pandas/service_class.tmpl +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/service/polars/functions.tmpl +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/service/polars/service.test.tmpl +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/templates/service/polars/service_class.tmpl +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/tester_task.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam/utils.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam_python.egg-info/SOURCES.txt +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam_python.egg-info/dependency_links.txt +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam_python.egg-info/entry_points.txt +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam_python.egg-info/requires.txt +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pam_python.egg-info/top_level.txt +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/pyproject.toml +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/setup.cfg +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_range_transform.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_report_store.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_result_batch_uploader.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_service_dataframe_output.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_sqlite_download_v2.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_area_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_bar_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_box_plot_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_bubble_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_candlestick_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_donut_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_heatmap_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_line_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_metric_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_pie_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_polar_area_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_radar_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_radial_bar_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_range_area_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_range_bar_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_scatter_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/tests/test_typed_table_report.py +0 -0
- {pam_python-0.2.1 → pam_python-0.2.2}/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.2
|
|
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
|
|
@@ -58,46 +58,42 @@ This README is a practical, step-by-step guide you can follow to create and run
|
|
|
58
58
|
|
|
59
59
|
---
|
|
60
60
|
|
|
61
|
-
**
|
|
62
|
-
|
|
61
|
+
**Create a uv project**
|
|
62
|
+
|
|
63
|
+
Install PAM inside each project instead of installing the CLI globally:
|
|
63
64
|
|
|
64
65
|
```bash
|
|
65
66
|
mkdir my_data_plugin
|
|
66
67
|
cd my_data_plugin
|
|
67
|
-
uv
|
|
68
|
+
uv init --python 3.12
|
|
69
|
+
uv add pam-python
|
|
70
|
+
uv run pam init
|
|
68
71
|
```
|
|
69
72
|
|
|
70
|
-
|
|
73
|
+
`pam init` recognizes a fresh uv project, keeps its uv configuration, and adds
|
|
74
|
+
the PAM scaffold. It refuses projects whose generated source files have already
|
|
75
|
+
been edited.
|
|
76
|
+
|
|
77
|
+
Pip remains supported for new projects that do not use uv:
|
|
71
78
|
|
|
72
79
|
```bash
|
|
80
|
+
mkdir my_data_plugin
|
|
81
|
+
cd my_data_plugin
|
|
73
82
|
python3 -m venv venv
|
|
74
83
|
source venv/bin/activate
|
|
75
84
|
python -m pip install pam-python
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
**Initialize a Project**
|
|
81
|
-
This creates a runnable project with templates, including `AGENT.md` for runtime
|
|
82
|
-
rules and `REPORTS.md` for the managed-report public API and copyable examples.
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
85
|
pam init
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
`pam init` asks
|
|
89
|
-
|
|
90
|
-
`requirements.txt`. The two templates are independent and are never mixed.
|
|
91
|
-
|
|
92
|
-
Initialization only supports a new project directory and will not migrate or
|
|
93
|
-
overwrite an existing project.
|
|
88
|
+
When run in an empty directory, `pam init` still asks whether to create a uv or
|
|
89
|
+
pip project. PAM never migrates an existing application.
|
|
94
90
|
|
|
95
91
|
---
|
|
96
92
|
|
|
97
93
|
**Create a Service**
|
|
98
94
|
|
|
99
95
|
```bash
|
|
100
|
-
pam new service rfm_segment
|
|
96
|
+
uv run pam new service rfm_segment
|
|
101
97
|
```
|
|
102
98
|
|
|
103
99
|
The generator asks you to select Polars (default) or Pandas. Polars services use
|
|
@@ -38,46 +38,42 @@ This README is a practical, step-by-step guide you can follow to create and run
|
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
41
|
-
**
|
|
42
|
-
|
|
41
|
+
**Create a uv project**
|
|
42
|
+
|
|
43
|
+
Install PAM inside each project instead of installing the CLI globally:
|
|
43
44
|
|
|
44
45
|
```bash
|
|
45
46
|
mkdir my_data_plugin
|
|
46
47
|
cd my_data_plugin
|
|
47
|
-
uv
|
|
48
|
+
uv init --python 3.12
|
|
49
|
+
uv add pam-python
|
|
50
|
+
uv run pam init
|
|
48
51
|
```
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
`pam init` recognizes a fresh uv project, keeps its uv configuration, and adds
|
|
54
|
+
the PAM scaffold. It refuses projects whose generated source files have already
|
|
55
|
+
been edited.
|
|
56
|
+
|
|
57
|
+
Pip remains supported for new projects that do not use uv:
|
|
51
58
|
|
|
52
59
|
```bash
|
|
60
|
+
mkdir my_data_plugin
|
|
61
|
+
cd my_data_plugin
|
|
53
62
|
python3 -m venv venv
|
|
54
63
|
source venv/bin/activate
|
|
55
64
|
python -m pip install pam-python
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
**Initialize a Project**
|
|
61
|
-
This creates a runnable project with templates, including `AGENT.md` for runtime
|
|
62
|
-
rules and `REPORTS.md` for the managed-report public API and copyable examples.
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
65
|
pam init
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
`pam init` asks
|
|
69
|
-
|
|
70
|
-
`requirements.txt`. The two templates are independent and are never mixed.
|
|
71
|
-
|
|
72
|
-
Initialization only supports a new project directory and will not migrate or
|
|
73
|
-
overwrite an existing project.
|
|
68
|
+
When run in an empty directory, `pam init` still asks whether to create a uv or
|
|
69
|
+
pip project. PAM never migrates an existing application.
|
|
74
70
|
|
|
75
71
|
---
|
|
76
72
|
|
|
77
73
|
**Create a Service**
|
|
78
74
|
|
|
79
75
|
```bash
|
|
80
|
-
pam new service rfm_segment
|
|
76
|
+
uv run pam new service rfm_segment
|
|
81
77
|
```
|
|
82
78
|
|
|
83
79
|
The generator asks you to select Polars (default) or Pandas. Polars services use
|
|
@@ -8,6 +8,7 @@ import shutil
|
|
|
8
8
|
import subprocess
|
|
9
9
|
import sys
|
|
10
10
|
import tempfile
|
|
11
|
+
import tomllib
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
PROJECT_MARKER = ".pam-project"
|
|
@@ -159,6 +160,67 @@ def _assert_new_project(directory: Path):
|
|
|
159
160
|
)
|
|
160
161
|
|
|
161
162
|
|
|
163
|
+
def _requirement_name(requirement: str) -> str:
|
|
164
|
+
return re.split(r"[<>=!~;\s\[]", requirement, maxsplit=1)[0].lower()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _assert_fresh_uv_project(directory: Path):
|
|
168
|
+
pyproject_path = directory / "pyproject.toml"
|
|
169
|
+
try:
|
|
170
|
+
project = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))["project"]
|
|
171
|
+
except (OSError, tomllib.TOMLDecodeError, KeyError, TypeError) as exc:
|
|
172
|
+
raise FileExistsError(f"Invalid fresh uv project: {exc}") from exc
|
|
173
|
+
|
|
174
|
+
dependencies = project.get("dependencies", [])
|
|
175
|
+
if not isinstance(dependencies, list) or not any(
|
|
176
|
+
isinstance(item, str) and _requirement_name(item) == "pam-python"
|
|
177
|
+
for item in dependencies
|
|
178
|
+
):
|
|
179
|
+
raise FileExistsError(
|
|
180
|
+
"Run `uv add pam-python` before `uv run pam init`"
|
|
181
|
+
)
|
|
182
|
+
|
|
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
|
+
|
|
189
|
+
allowed = {
|
|
190
|
+
".git",
|
|
191
|
+
".gitignore",
|
|
192
|
+
".python-version",
|
|
193
|
+
".venv",
|
|
194
|
+
"README.md",
|
|
195
|
+
"main.py",
|
|
196
|
+
"pyproject.toml",
|
|
197
|
+
"uv.lock",
|
|
198
|
+
}
|
|
199
|
+
unexpected = sorted(path.name for path in directory.iterdir() if path.name not in allowed)
|
|
200
|
+
if unexpected:
|
|
201
|
+
raise FileExistsError(
|
|
202
|
+
"pam init only accepts a fresh uv project; existing files: "
|
|
203
|
+
+ ", ".join(unexpected)
|
|
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,
|
|
219
|
+
)
|
|
220
|
+
if uv_placeholder is None:
|
|
221
|
+
raise FileExistsError("pam init will not overwrite an edited main.py")
|
|
222
|
+
|
|
223
|
+
|
|
162
224
|
def _run_uv(args: list[str], cwd: Path):
|
|
163
225
|
uv = shutil.which("uv")
|
|
164
226
|
if uv is None:
|
|
@@ -167,12 +229,20 @@ def _run_uv(args: list[str], cwd: Path):
|
|
|
167
229
|
|
|
168
230
|
|
|
169
231
|
def init_project(package_manager: str | None = None):
|
|
170
|
-
|
|
232
|
+
destination = Path.cwd()
|
|
233
|
+
preinitialized_uv = (destination / "pyproject.toml").is_file()
|
|
234
|
+
if preinitialized_uv:
|
|
235
|
+
if package_manager not in (None, "uv"):
|
|
236
|
+
raise FileExistsError("A uv project cannot be initialized as a pip project")
|
|
237
|
+
_assert_fresh_uv_project(destination)
|
|
238
|
+
package_manager = "uv"
|
|
239
|
+
else:
|
|
240
|
+
package_manager = package_manager or _select_package_manager()
|
|
241
|
+
_assert_new_project(destination)
|
|
242
|
+
|
|
171
243
|
if package_manager not in PACKAGE_MANAGER_TEMPLATES:
|
|
172
244
|
raise ValueError(f"Unsupported package manager: {package_manager}")
|
|
173
245
|
|
|
174
|
-
destination = Path.cwd()
|
|
175
|
-
_assert_new_project(destination)
|
|
176
246
|
if package_manager == "uv" and shutil.which("uv") is None:
|
|
177
247
|
raise RuntimeError("uv is required for this project but was not found on PATH")
|
|
178
248
|
|
|
@@ -184,6 +254,11 @@ def init_project(package_manager: str | None = None):
|
|
|
184
254
|
**COMMON_PROJECT_TEMPLATES,
|
|
185
255
|
**PACKAGE_MANAGER_TEMPLATES[package_manager],
|
|
186
256
|
}
|
|
257
|
+
if preinitialized_uv:
|
|
258
|
+
templates = {
|
|
259
|
+
src: dest for src, dest in templates.items()
|
|
260
|
+
if dest not in {".gitignore", ".python-version", "pyproject.toml"}
|
|
261
|
+
}
|
|
187
262
|
|
|
188
263
|
with tempfile.TemporaryDirectory(prefix=".pam-init-", dir=destination) as temp_dir:
|
|
189
264
|
staging = Path(temp_dir)
|
|
@@ -202,7 +277,7 @@ def init_project(package_manager: str | None = None):
|
|
|
202
277
|
encoding="utf-8",
|
|
203
278
|
)
|
|
204
279
|
|
|
205
|
-
if package_manager == "uv":
|
|
280
|
+
if package_manager == "uv" and not preinitialized_uv:
|
|
206
281
|
_run_uv(["lock"], staging)
|
|
207
282
|
|
|
208
283
|
for path in staging.iterdir():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pam-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
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
|
|
@@ -58,46 +58,42 @@ This README is a practical, step-by-step guide you can follow to create and run
|
|
|
58
58
|
|
|
59
59
|
---
|
|
60
60
|
|
|
61
|
-
**
|
|
62
|
-
|
|
61
|
+
**Create a uv project**
|
|
62
|
+
|
|
63
|
+
Install PAM inside each project instead of installing the CLI globally:
|
|
63
64
|
|
|
64
65
|
```bash
|
|
65
66
|
mkdir my_data_plugin
|
|
66
67
|
cd my_data_plugin
|
|
67
|
-
uv
|
|
68
|
+
uv init --python 3.12
|
|
69
|
+
uv add pam-python
|
|
70
|
+
uv run pam init
|
|
68
71
|
```
|
|
69
72
|
|
|
70
|
-
|
|
73
|
+
`pam init` recognizes a fresh uv project, keeps its uv configuration, and adds
|
|
74
|
+
the PAM scaffold. It refuses projects whose generated source files have already
|
|
75
|
+
been edited.
|
|
76
|
+
|
|
77
|
+
Pip remains supported for new projects that do not use uv:
|
|
71
78
|
|
|
72
79
|
```bash
|
|
80
|
+
mkdir my_data_plugin
|
|
81
|
+
cd my_data_plugin
|
|
73
82
|
python3 -m venv venv
|
|
74
83
|
source venv/bin/activate
|
|
75
84
|
python -m pip install pam-python
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
**Initialize a Project**
|
|
81
|
-
This creates a runnable project with templates, including `AGENT.md` for runtime
|
|
82
|
-
rules and `REPORTS.md` for the managed-report public API and copyable examples.
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
85
|
pam init
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
`pam init` asks
|
|
89
|
-
|
|
90
|
-
`requirements.txt`. The two templates are independent and are never mixed.
|
|
91
|
-
|
|
92
|
-
Initialization only supports a new project directory and will not migrate or
|
|
93
|
-
overwrite an existing project.
|
|
88
|
+
When run in an empty directory, `pam init` still asks whether to create a uv or
|
|
89
|
+
pip project. PAM never migrates an existing application.
|
|
94
90
|
|
|
95
91
|
---
|
|
96
92
|
|
|
97
93
|
**Create a Service**
|
|
98
94
|
|
|
99
95
|
```bash
|
|
100
|
-
pam new service rfm_segment
|
|
96
|
+
uv run pam new service rfm_segment
|
|
101
97
|
```
|
|
102
98
|
|
|
103
99
|
The generator asks you to select Polars (default) or Pandas. Polars services use
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import os
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
import sys
|
|
@@ -114,6 +115,57 @@ class CLITemplateTest(unittest.TestCase):
|
|
|
114
115
|
calls,
|
|
115
116
|
)
|
|
116
117
|
|
|
118
|
+
def test_init_adopts_a_fresh_uv_project_without_rewriting_uv_files(self):
|
|
119
|
+
calls = []
|
|
120
|
+
original_pyproject = """[project]
|
|
121
|
+
name = "customer-plugin"
|
|
122
|
+
version = "0.1.0"
|
|
123
|
+
requires-python = ">=3.12"
|
|
124
|
+
dependencies = ["pam-python>=0.2.1"]
|
|
125
|
+
"""
|
|
126
|
+
uv_main = '''def main():
|
|
127
|
+
print("Hello from customer-plugin!")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
main()
|
|
132
|
+
'''
|
|
133
|
+
|
|
134
|
+
with tempfile.TemporaryDirectory() as temp_dir, WorkingDirectory(temp_dir):
|
|
135
|
+
root = Path(temp_dir)
|
|
136
|
+
(root / "pyproject.toml").write_text(original_pyproject)
|
|
137
|
+
(root / ".python-version").write_text("3.12\n")
|
|
138
|
+
(root / "uv.lock").write_text("version = 1\n")
|
|
139
|
+
(root / ".gitignore").write_text(".venv\n")
|
|
140
|
+
(root / "README.md").touch()
|
|
141
|
+
(root / "main.py").write_text(uv_main)
|
|
142
|
+
|
|
143
|
+
with mock.patch.object(cli.shutil, "which", return_value="/usr/bin/uv"), mock.patch.object(
|
|
144
|
+
cli, "_run_uv", side_effect=lambda args, cwd: calls.append(list(args))
|
|
145
|
+
):
|
|
146
|
+
cli.init_project()
|
|
147
|
+
|
|
148
|
+
self.assertEqual(original_pyproject, (root / "pyproject.toml").read_text())
|
|
149
|
+
self.assertEqual("3.12\n", (root / ".python-version").read_text())
|
|
150
|
+
self.assertEqual("version = 1\n", (root / "uv.lock").read_text())
|
|
151
|
+
self.assertEqual(".venv\n", (root / ".gitignore").read_text())
|
|
152
|
+
self.assertIn("plugin_server = Server", (root / "main.py").read_text())
|
|
153
|
+
self.assertEqual("uv", json.loads((root / ".pam-project").read_text())["package_manager"])
|
|
154
|
+
self.assertEqual([], calls)
|
|
155
|
+
|
|
156
|
+
def test_init_rejects_an_edited_uv_project(self):
|
|
157
|
+
with tempfile.TemporaryDirectory() as temp_dir, WorkingDirectory(temp_dir):
|
|
158
|
+
root = Path(temp_dir)
|
|
159
|
+
(root / "pyproject.toml").write_text(
|
|
160
|
+
'[project]\nname = "plugin"\nversion = "0.1.0"\n'
|
|
161
|
+
'dependencies = ["pam-python"]\n'
|
|
162
|
+
)
|
|
163
|
+
(root / ".python-version").write_text("3.12\n")
|
|
164
|
+
(root / "main.py").write_text("print('real code')\n")
|
|
165
|
+
|
|
166
|
+
with self.assertRaisesRegex(FileExistsError, "edited main.py"):
|
|
167
|
+
cli.init_project()
|
|
168
|
+
|
|
117
169
|
def test_init_rejects_an_existing_project_without_overwriting(self):
|
|
118
170
|
with tempfile.TemporaryDirectory() as temp_dir, WorkingDirectory(temp_dir):
|
|
119
171
|
original = Path("main.py")
|
|
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
|