stml-cli 0.1.6__tar.gz → 0.1.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.
Files changed (24) hide show
  1. {stml_cli-0.1.6 → stml_cli-0.1.8}/PKG-INFO +1 -1
  2. {stml_cli-0.1.6 → stml_cli-0.1.8}/pyproject.toml +5 -0
  3. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli/__init__.py +1 -1
  4. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli/commands.py +78 -3
  5. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli/paths.py +3 -2
  6. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli.egg-info/PKG-INFO +1 -1
  7. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli.egg-info/SOURCES.txt +1 -0
  8. stml_cli-0.1.8/tests/test_init.py +42 -0
  9. {stml_cli-0.1.6 → stml_cli-0.1.8}/tests/test_paths.py +12 -0
  10. {stml_cli-0.1.6 → stml_cli-0.1.8}/README.md +0 -0
  11. {stml_cli-0.1.6 → stml_cli-0.1.8}/setup.cfg +0 -0
  12. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli/__main__.py +0 -0
  13. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli/config.py +0 -0
  14. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli/http.py +0 -0
  15. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli/oauth.py +0 -0
  16. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli/packaging.py +0 -0
  17. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli/refs.py +0 -0
  18. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli.egg-info/dependency_links.txt +0 -0
  19. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli.egg-info/entry_points.txt +0 -0
  20. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli.egg-info/requires.txt +0 -0
  21. {stml_cli-0.1.6 → stml_cli-0.1.8}/stml_cli.egg-info/top_level.txt +0 -0
  22. {stml_cli-0.1.6 → stml_cli-0.1.8}/tests/test_disambiguation.py +0 -0
  23. {stml_cli-0.1.6 → stml_cli-0.1.8}/tests/test_refs.py +0 -0
  24. {stml_cli-0.1.6 → stml_cli-0.1.8}/tests/test_whoami_expiry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stml-cli
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Summary: The stml partner CLI — pull, edit, push, and publish stml apps and libraries.
5
5
  Author: stml
6
6
  Project-URL: Homepage, https://stml.io
@@ -25,3 +25,8 @@ version = { attr = "stml_cli.__version__" }
25
25
  [tool.setuptools.packages.find]
26
26
  where = ["."]
27
27
  include = ["stml_cli*"]
28
+
29
+ [dependency-groups]
30
+ dev = [
31
+ "pytest>=8",
32
+ ]
@@ -9,7 +9,7 @@ from __future__ import annotations
9
9
 
10
10
  import os
11
11
 
12
- __version__ = "0.1.6"
12
+ __version__ = "0.1.8"
13
13
 
14
14
  # TLS trust: framework/python.org builds have no OpenSSL CA path, so a real
15
15
  # https backend fails with "unable to get local issuer certificate". certifi's
@@ -152,6 +152,16 @@ def cmd_pull(args) -> None:
152
152
  _write_link(dest, args.backend, workspace, app)
153
153
  tail = f" (+ {asset_count} asset(s))" if asset_count else ""
154
154
  _log(f"✓ Pulled {len(written)} file(s) from {result.get('app', app)} → {dest}{tail}")
155
+ if result.get("generated_manifest"):
156
+ # The app's overlay had no manifest — the backend reconstructed one from
157
+ # the platform's live state (flow rows, dependency pins, provenance).
158
+ _log(" ℹ pyproject.toml was generated from the app's platform state — "
159
+ "review it (name/version/issuer-org) before `stml publish`.")
160
+ if result.get("base_files"):
161
+ # Attached app: files the app never forked come from its base library
162
+ # version, so the folder is the complete effective tree.
163
+ _log(f" ℹ {result['base_files']} file(s) came from the app's base "
164
+ "library (never forked in the app).")
155
165
 
156
166
 
157
167
  def cmd_push(args) -> None:
@@ -305,6 +315,7 @@ def cmd_init(args) -> None:
305
315
  raise SystemExit(f"{target} exists and is not empty")
306
316
  target.mkdir(parents=True, exist_ok=True)
307
317
  name = args.name or target.name
318
+ pkg_name = name.replace("-", "_")
308
319
  (target / "pyproject.toml").write_text(
309
320
  f'''[project]
310
321
  name = "{name}"
@@ -316,15 +327,79 @@ dependencies = []
316
327
  [tool.stml]
317
328
  requires-runtime = ">=0.1"
318
329
  issuer-org = "" # set to your org slug
319
- exports-flows = []
330
+ exports-flows = [
331
+ {{ name = "hello", entrypoint = "{pkg_name}.hello:app" }},
332
+ ]
320
333
  depends-on = []
334
+
335
+ [dependency-groups]
336
+ dev = [
337
+ "pytest",
338
+ ]
339
+
340
+ [tool.pytest.ini_options]
341
+ pythonpath = ["src"]
321
342
  ''',
322
343
  encoding="utf-8",
323
344
  )
324
- pkg = target / name.replace("-", "_")
325
- pkg.mkdir()
345
+ # src layout — the shape `stml pull` writes and deployment-version 2
346
+ # expects (flows ship under src/); keeps init/pull/push round-trips stable.
347
+ pkg = target / "src" / pkg_name
348
+ pkg.mkdir(parents=True)
326
349
  (pkg / "__init__.py").write_text('"""Library entry point."""\n', encoding="utf-8")
350
+ (pkg / "hello.py").write_text(
351
+ f'''"""A minimal runnable flow — replace its steps with your own and keep
352
+ `exports-flows` in pyproject.toml pointing at each flow's `module:app`."""
353
+
354
+ from stml.runtime import App
355
+
356
+ app = App("hello")
357
+
358
+
359
+ @app.step(name="Greet", description="Says hello.")
360
+ def greet() -> dict:
361
+ return {{"message": "Hello from {name}!"}}
362
+ ''',
363
+ encoding="utf-8",
364
+ )
327
365
  (target / "README.md").write_text(f"# {name}\n", encoding="utf-8")
366
+ (target / "AGENTS.md").write_text(
367
+ f'''# `{name}` — usage for the assistant
368
+
369
+ <What this library does, who installs it, and how its flows are meant to be
370
+ run — write it for the AI assistant working on or with this library.>
371
+
372
+ ## Layout
373
+
374
+ - `src/{pkg_name}/` — the package. Flows live here and are listed in
375
+ `pyproject.toml` under `[tool.stml] exports-flows` as `module:app`.
376
+ - `tests/` — local-only: `stml push` and `stml publish` both skip it.
377
+ Run with `uv run pytest`.
378
+ ''',
379
+ encoding="utf-8",
380
+ )
381
+ (target / ".gitignore").write_text(
382
+ ".venv/\n__pycache__/\n*.pyc\n.stml/\n.pytest_cache/\n.ruff_cache/\n",
383
+ encoding="utf-8",
384
+ )
385
+ # tests/ is LOCAL-ONLY by convention: push and publish both skip it
386
+ # (paths._EXCLUDE_DIRS / packaging.EXCLUDE_DIRS), so scaffold it with a
387
+ # stub that says so — runnable code belongs in the package, never here.
388
+ tests_dir = target / "tests"
389
+ tests_dir.mkdir()
390
+ (tests_dir / "__init__.py").write_text("", encoding="utf-8")
391
+ (tests_dir / "test_smoke.py").write_text(
392
+ f'''"""tests/ is local-only: `stml push` and `stml publish` both skip this
393
+ folder — put runnable code in the package, never in here."""
394
+
395
+ import {pkg_name}
396
+
397
+
398
+ def test_import():
399
+ assert {pkg_name}.__doc__
400
+ ''',
401
+ encoding="utf-8",
402
+ )
328
403
  _log(f"✓ Scaffolded {target}")
329
404
 
330
405
 
@@ -47,9 +47,10 @@ def _is_packaging_path(rel: str) -> bool:
47
47
  return True
48
48
  return False
49
49
 
50
- # Local-only artifacts that must never be pushed into the overlay.
50
+ # Local-only artifacts that must never be pushed into the overlay. `tests`
51
+ # matches packaging.EXCLUDE_DIRS: unit tests are test-only, never shipped.
51
52
  _EXCLUDE_DIRS = {
52
- ".git", "__pycache__", ".venv", "venv", ".stml",
53
+ ".git", "__pycache__", ".venv", "venv", ".stml", "tests",
53
54
  "node_modules", ".mypy_cache", ".ruff_cache", ".pytest_cache",
54
55
  }
55
56
  _EXCLUDE_NAMES = {".DS_Store"}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stml-cli
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Summary: The stml partner CLI — pull, edit, push, and publish stml apps and libraries.
5
5
  Author: stml
6
6
  Project-URL: Homepage, https://stml.io
@@ -16,6 +16,7 @@ stml_cli.egg-info/entry_points.txt
16
16
  stml_cli.egg-info/requires.txt
17
17
  stml_cli.egg-info/top_level.txt
18
18
  tests/test_disambiguation.py
19
+ tests/test_init.py
19
20
  tests/test_paths.py
20
21
  tests/test_refs.py
21
22
  tests/test_whoami_expiry.py
@@ -0,0 +1,42 @@
1
+ from pathlib import Path
2
+ from types import SimpleNamespace
3
+
4
+ from stml_cli import commands, paths
5
+
6
+
7
+ def test_init_scaffolds_src_layout_lib(tmp_path: Path):
8
+ target = tmp_path / "my-lib"
9
+ commands.cmd_init(SimpleNamespace(dir=str(target), name=None))
10
+
11
+ # src layout with a runnable example flow, exported in pyproject.
12
+ assert (target / "src" / "my_lib" / "__init__.py").is_file()
13
+ assert (target / "src" / "my_lib" / "hello.py").is_file()
14
+ pyproject = (target / "pyproject.toml").read_text()
15
+ assert 'entrypoint = "my_lib.hello:app"' in pyproject
16
+ # pytest works out of the box: dev dep + src on the test pythonpath.
17
+ assert "pytest" in pyproject and 'pythonpath = ["src"]' in pyproject
18
+ assert (target / "AGENTS.md").is_file()
19
+ assert (target / ".gitignore").is_file()
20
+
21
+ # tests/ is scaffolded but local-only: the stub imports the package name
22
+ # and push (read_tree) must not pick the folder up.
23
+ stub = (target / "tests" / "test_smoke.py").read_text()
24
+ assert "import my_lib" in stub
25
+ keys = {f["path"] for f in paths.read_tree(target)}
26
+ assert "my_lib/hello.py" in keys # src/ stripped to flat overlay keys
27
+ assert not any(k.startswith("tests/") for k in keys)
28
+
29
+
30
+ def test_init_scaffolded_smoke_test_passes(tmp_path: Path):
31
+ """The generated tests/test_smoke.py must actually pass in the scaffold."""
32
+ import subprocess
33
+ import sys
34
+
35
+ target = tmp_path / "demo-lib"
36
+ commands.cmd_init(SimpleNamespace(dir=str(target), name=None))
37
+ res = subprocess.run(
38
+ [sys.executable, "-m", "pytest", "tests/", "-q", "-p", "no:cacheprovider",
39
+ "--rootdir", str(target), "-o", 'pythonpath=src'],
40
+ cwd=target, capture_output=True, text=True,
41
+ )
42
+ assert res.returncode == 0, res.stdout + res.stderr
@@ -114,6 +114,18 @@ def test_local_junk_is_excluded(tmp_path: Path):
114
114
  assert keys == {"flows/x.py"} # .git, __pycache__, .stml, *.pyc all dropped
115
115
 
116
116
 
117
+ def test_tests_dir_is_never_pushed(tmp_path: Path):
118
+ """tests/ is test-only and never shipped — push skips it like publish does
119
+ (packaging.EXCLUDE_DIRS), both top-level and under src/."""
120
+ paths.write_tree(tmp_path, [{"path": "flows/x.py", "content": "x=1\n"}])
121
+ (tmp_path / "tests").mkdir()
122
+ (tmp_path / "tests" / "test_x.py").write_text("def test_x(): pass\n")
123
+ (tmp_path / "src" / "tests").mkdir()
124
+ (tmp_path / "src" / "tests" / "test_y.py").write_text("def test_y(): pass\n")
125
+ keys = {f["path"] for f in paths.read_tree(tmp_path)}
126
+ assert keys == {"flows/x.py"}
127
+
128
+
117
129
  def test_assets_dir_is_never_pushed(tmp_path: Path):
118
130
  """Top-level assets/ is bucket-backed binary media (written by pull) — push
119
131
  must skip it (a .xlsx would crash read_text) but publish ships it from disk."""
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