pam-python 0.2.1__py3-none-any.whl → 0.2.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.
pam/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """PAM Data Plugin framework."""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.2.2"
pam/cli.py CHANGED
@@ -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
- package_manager = package_manager or _select_package_manager()
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.1
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
- **Install**
62
- Recommended installation:
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 tool install pam-python
68
+ uv init --python 3.12
69
+ uv add pam-python
70
+ uv run pam init
68
71
  ```
69
72
 
70
- Pip remains supported:
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 you to select `uv` (default) or `pip`. A uv project contains
89
- `pyproject.toml`, `.python-version`, and `uv.lock`; a pip project contains
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,6 +1,6 @@
1
- pam/__init__.py,sha256=HkDyjbFKSTG9cYUIHwpisICJbo99Cl8uvvTA7cva-wg,56
1
+ pam/__init__.py,sha256=vwt2aChyIDuFICQ8ZWjM1dPatwHoyi1kwsjGs8LoLhE,56
2
2
  pam/api.py,sha256=4t64ey_Jc4gC3SvfI-mtedEGzkX2cPTt0cdZgP_huLk,7231
3
- pam/cli.py,sha256=1jzS7a4Tz9SSIDraSdIQ88AhpAKMMMvtjiRmdQT4umU,11381
3
+ pam/cli.py,sha256=GbTyHD2Gc2WLTY54bZ9rtofjVc7bV2JjqXThR9JCQf4,14130
4
4
  pam/interface_task_manager.py,sha256=moKUjSpAeNF9C2dluDFPAdU4IyvwXckB0RK-jINTLm4,1665
5
5
  pam/logger.py,sha256=JYPfIpaQZy6QrfVKrzEMrxOgTzWIEhoegvIZgHTGWoc,3021
6
6
  pam/request_file_format.py,sha256=fSRlJagbvVGqdIEqhINSeqBNUTNQdm8GL5JfqgX9bxU,317
@@ -69,9 +69,9 @@ pam/templates/service/pandas/service_class.tmpl,sha256=LGgIYjfdsBwppOqP-FfgEsF1k
69
69
  pam/templates/service/polars/functions.tmpl,sha256=nMKFC5GqIm2Q1dSg2zDOscSwQaOpzS2jzqjQV7xhPRk,382
70
70
  pam/templates/service/polars/service.test.tmpl,sha256=58x_MJYwp7BvDtuSsETAt9Q6v5KwOI8MaEdjNciAQDk,3746
71
71
  pam/templates/service/polars/service_class.tmpl,sha256=8FDTFb6I5OHFqM0KPJtYDFAHeL3kl2WbwtbHAjwTG4Y,1830
72
- pam_python-0.2.1.dist-info/licenses/LICENSE.txt,sha256=yeHD2-gzrkkET2ZkMe22WyUGpDjlwTrqA4POb0FoZi4,1138
73
- pam_python-0.2.1.dist-info/METADATA,sha256=fPvsI78skyXPDA5yh_a2dZopS9xLmYa7TY2HftO9a4s,8730
74
- pam_python-0.2.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
75
- pam_python-0.2.1.dist-info/entry_points.txt,sha256=xqYpsIqvNlO0j8elZc02bDwR-12dLK9FU2B1ACsLYxw,37
76
- pam_python-0.2.1.dist-info/top_level.txt,sha256=0EOjbyc3hQyzjhn6iyMgsEseqA66Xz0p27iBN7G7W1w,4
77
- pam_python-0.2.1.dist-info/RECORD,,
72
+ pam_python-0.2.2.dist-info/licenses/LICENSE.txt,sha256=yeHD2-gzrkkET2ZkMe22WyUGpDjlwTrqA4POb0FoZi4,1138
73
+ pam_python-0.2.2.dist-info/METADATA,sha256=MWstqmu0l3XRLjBamIEM0p-uSf0UBHmX1-V2kczonVM,8669
74
+ pam_python-0.2.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
75
+ pam_python-0.2.2.dist-info/entry_points.txt,sha256=xqYpsIqvNlO0j8elZc02bDwR-12dLK9FU2B1ACsLYxw,37
76
+ pam_python-0.2.2.dist-info/top_level.txt,sha256=0EOjbyc3hQyzjhn6iyMgsEseqA66Xz0p27iBN7G7W1w,4
77
+ pam_python-0.2.2.dist-info/RECORD,,