programasweights 0.2.8__tar.gz → 0.3.0__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 (61) hide show
  1. {programasweights-0.2.8 → programasweights-0.3.0}/AGENTS.md +20 -1
  2. {programasweights-0.2.8 → programasweights-0.3.0}/PKG-INFO +1 -1
  3. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/__init__.py +67 -11
  4. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/client.py +14 -0
  5. {programasweights-0.2.8 → programasweights-0.3.0}/pyproject.toml +1 -1
  6. {programasweights-0.2.8 → programasweights-0.3.0}/.gitignore +0 -0
  7. {programasweights-0.2.8 → programasweights-0.3.0}/.readthedocs.yaml +0 -0
  8. {programasweights-0.2.8 → programasweights-0.3.0}/CHANGELOG.md +0 -0
  9. {programasweights-0.2.8 → programasweights-0.3.0}/LICENSE +0 -0
  10. {programasweights-0.2.8 → programasweights-0.3.0}/PYPI_README.md +0 -0
  11. {programasweights-0.2.8 → programasweights-0.3.0}/README.md +0 -0
  12. {programasweights-0.2.8 → programasweights-0.3.0}/docs/adr/001-llama-cpp-over-pytorch.md +0 -0
  13. {programasweights-0.2.8 → programasweights-0.3.0}/docs/adr/002-q4_0-adapter-format.md +0 -0
  14. {programasweights-0.2.8 → programasweights-0.3.0}/docs/adr/003-single-spec-field.md +0 -0
  15. {programasweights-0.2.8 → programasweights-0.3.0}/docs/adr/004-compiler-naming.md +0 -0
  16. {programasweights-0.2.8 → programasweights-0.3.0}/docs/adr/005-vllm-hidden-states.md +0 -0
  17. {programasweights-0.2.8 → programasweights-0.3.0}/docs/adr/006-email-api-key-auth.md +0 -0
  18. {programasweights-0.2.8 → programasweights-0.3.0}/docs/advanced/adrs.md +0 -0
  19. {programasweights-0.2.8 → programasweights-0.3.0}/docs/advanced/architecture.md +0 -0
  20. {programasweights-0.2.8 → programasweights-0.3.0}/docs/api-reference/cli.md +0 -0
  21. {programasweights-0.2.8 → programasweights-0.3.0}/docs/api-reference/python-sdk.md +0 -0
  22. {programasweights-0.2.8 → programasweights-0.3.0}/docs/api-reference/rest-api.md +0 -0
  23. {programasweights-0.2.8 → programasweights-0.3.0}/docs/architecture.md +0 -0
  24. {programasweights-0.2.8 → programasweights-0.3.0}/docs/case-studies/log-monitoring.md +0 -0
  25. {programasweights-0.2.8 → programasweights-0.3.0}/docs/case-studies/semantic-search.md +0 -0
  26. {programasweights-0.2.8 → programasweights-0.3.0}/docs/case-studies/site-navigation.md +0 -0
  27. {programasweights-0.2.8 → programasweights-0.3.0}/docs/case-studies/tool-calling.md +0 -0
  28. {programasweights-0.2.8 → programasweights-0.3.0}/docs/getting-started/first-program.md +0 -0
  29. {programasweights-0.2.8 → programasweights-0.3.0}/docs/getting-started/installation.md +0 -0
  30. {programasweights-0.2.8 → programasweights-0.3.0}/docs/getting-started/naming-programs.md +0 -0
  31. {programasweights-0.2.8 → programasweights-0.3.0}/docs/guide/browser-inference.md +0 -0
  32. {programasweights-0.2.8 → programasweights-0.3.0}/docs/guide/how-it-works.md +0 -0
  33. {programasweights-0.2.8 → programasweights-0.3.0}/docs/guide/local-inference.md +0 -0
  34. {programasweights-0.2.8 → programasweights-0.3.0}/docs/guide/writing-good-specs.md +0 -0
  35. {programasweights-0.2.8 → programasweights-0.3.0}/docs/hub/browsing-programs.md +0 -0
  36. {programasweights-0.2.8 → programasweights-0.3.0}/docs/hub/feedback-cases.md +0 -0
  37. {programasweights-0.2.8 → programasweights-0.3.0}/docs/hub/publishing-programs.md +0 -0
  38. {programasweights-0.2.8 → programasweights-0.3.0}/docs/index.md +0 -0
  39. {programasweights-0.2.8 → programasweights-0.3.0}/docs/requirements.txt +0 -0
  40. {programasweights-0.2.8 → programasweights-0.3.0}/examples/flask_app.py +0 -0
  41. {programasweights-0.2.8 → programasweights-0.3.0}/examples/jupyter_notebook.py +0 -0
  42. {programasweights-0.2.8 → programasweights-0.3.0}/examples/langchain_integration.py +0 -0
  43. {programasweights-0.2.8 → programasweights-0.3.0}/examples/paw_monitor.py +0 -0
  44. {programasweights-0.2.8 → programasweights-0.3.0}/examples/replace_openai.py +0 -0
  45. {programasweights-0.2.8 → programasweights-0.3.0}/mkdocs.yml +0 -0
  46. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/_output.py +0 -0
  47. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/artifacts.py +0 -0
  48. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/cache.py +0 -0
  49. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/cli.py +0 -0
  50. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/compiler/__init__.py +0 -0
  51. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/compiler/dummy.py +0 -0
  52. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/config.py +0 -0
  53. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/convert_peft_to_paw.py +0 -0
  54. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/paw_format.py +0 -0
  55. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/runtime/__init__.py +0 -0
  56. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/runtime/interpreter.py +0 -0
  57. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/runtime/interpreter_onnx.py +0 -0
  58. {programasweights-0.2.8 → programasweights-0.3.0}/programasweights/runtime_llamacpp.py +0 -0
  59. {programasweights-0.2.8 → programasweights-0.3.0}/tests/test_offline_cache.py +0 -0
  60. {programasweights-0.2.8 → programasweights-0.3.0}/tests/test_sdk.py +0 -0
  61. {programasweights-0.2.8 → programasweights-0.3.0}/tests/test_sdk.sh +0 -0
@@ -161,6 +161,22 @@ Generate API keys at https://programasweights.com/settings.
161
161
 
162
162
  Commands: `paw compile --spec "..." --json`, `paw run --program <id> --input "..."`, `paw info <id>`, `paw rename <id> <slug>`, `paw login`. All support `--json` for structured output.
163
163
 
164
+ ## Versioning
165
+
166
+ Slugs support version history. Recompiling with the same slug creates a new version:
167
+
168
+ ```python
169
+ p1 = paw.compile("Count words v1", slug="word-counter") # v1
170
+ p2 = paw.compile("Count words v2", slug="word-counter") # v2 (auto-bumps)
171
+
172
+ fn = paw.function("da03/word-counter") # resolves to main (latest)
173
+ fn = paw.function("da03/word-counter@v1") # pinned to v1
174
+
175
+ versions = paw.list_versions("da03/word-counter") # all versions
176
+ ```
177
+
178
+ Pinned versions (`@v1`) are immutable and cached locally forever. Bare slugs always check the server for the latest main version (falls back to cache if offline).
179
+
164
180
  ## Full API Reference
165
181
 
166
182
  ```python
@@ -170,16 +186,19 @@ program = paw.compile(
170
186
  slug=None, # URL-safe handle (requires auth)
171
187
  public=True, # list on public hub
172
188
  )
173
- # Returns: Program(id, slug, status, timings, error)
189
+ # Returns: Program(id, slug, status, version, version_action, timings, error)
174
190
 
175
191
  fn = paw.function(program) # accepts Program object, hash ID, or slug
176
192
  fn = paw.function("a6b454023d41ac9ca845")
177
193
  fn = paw.function("da03/my-classifier")
194
+ fn = paw.function("da03/my-classifier@v2") # pinned version
195
+ fn = paw.function("da03/my-classifier", offline=True) # skip server check
178
196
 
179
197
  result: str = fn(input_text: str, max_tokens=None, temperature=0.0)
180
198
 
181
199
  fn = paw.compile_and_load(spec, compiler="paw-4b-qwen3-0.6b")
182
200
 
201
+ versions = paw.list_versions("da03/my-classifier") # version history
183
202
  programs = paw.list_programs(sort="recent", per_page=20) # requires auth
184
203
 
185
204
  paw.login()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: programasweights
3
- Version: 0.2.8
3
+ Version: 0.3.0
4
4
  Summary: Compile natural language specifications into neural programs that run locally via llama.cpp.
5
5
  Project-URL: Homepage, https://programasweights.com
6
6
  Project-URL: Repository, https://github.com/programasweights/programasweights-python
@@ -27,7 +27,7 @@ try:
27
27
  from importlib.metadata import version as _meta_version
28
28
  __version__ = _meta_version("programasweights")
29
29
  except Exception:
30
- __version__ = "0.2.8"
30
+ __version__ = "0.3.0"
31
31
 
32
32
  from .config import get_api_url, get_api_key, set_api_key
33
33
 
@@ -74,7 +74,13 @@ def compile(
74
74
  result = client.compile(spec, compiler=compiler, name=name, tags=tags, public=public, slug=slug)
75
75
  label = f"{result.id}"
76
76
  if result.slug:
77
- label += f" ({result.slug})"
77
+ ver_str = f" v{result.version}" if result.version and result.version > 1 else ""
78
+ action_str = ""
79
+ if result.version_action == "no_change":
80
+ action_str = " (no changes)"
81
+ elif result.version_action == "promoted":
82
+ action_str = " set as main"
83
+ label = f"{result.slug}{ver_str}{action_str}"
78
84
  status(f"Compiled: {label}")
79
85
  return result
80
86
 
@@ -84,6 +90,7 @@ def function(
84
90
  n_ctx: int = 2048,
85
91
  n_gpu_layers: int | None = None,
86
92
  verbose: bool = False,
93
+ offline: bool = False,
87
94
  ):
88
95
  """Load a compiled program for local inference via llama.cpp.
89
96
 
@@ -91,11 +98,14 @@ def function(
91
98
  Subsequent calls use the local cache.
92
99
 
93
100
  Args:
94
- program_id: Program ID (str), slug, or a ``Program`` object from compile().
101
+ program_id: Program ID (str), slug (``da03/my-program``), pinned version
102
+ (``da03/my-program@v3``), or a ``Program`` object from compile().
95
103
  n_ctx: Context window size for llama.cpp.
96
104
  n_gpu_layers: GPU layers (-1 = all, 0 = CPU only). Defaults to CPU.
97
105
  Set ``PAW_GPU_LAYERS`` env var or pass explicitly for GPU acceleration.
98
106
  verbose: Print llama.cpp debug output.
107
+ offline: Skip server check for slug resolution and use local cache only.
108
+ Also set via ``PAW_OFFLINE=1`` env var.
99
109
 
100
110
  Returns:
101
111
  A callable ``PawFunction`` that takes an input string and returns output.
@@ -105,8 +115,7 @@ def function(
105
115
  >>> fn("Urgent: the server is down!")
106
116
  'immediate'
107
117
 
108
- >>> program = paw.compile("Classify sentiment")
109
- >>> fn = paw.function(program) # accepts Program object directly
118
+ >>> fn = paw.function("da03/my-program@v2") # pinned version
110
119
  """
111
120
  if hasattr(program_id, 'id'):
112
121
  program_id = program_id.slug or program_id.id
@@ -122,15 +131,42 @@ def function(
122
131
 
123
132
  resolved_id = program_id
124
133
  if not re.fullmatch(r"[a-f0-9]{16,64}", program_id):
125
- cached = get_cached_slug(program_id)
126
- if cached:
127
- resolved_id = cached
134
+ is_pinned = bool(re.search(r"@v\d+$", program_id))
135
+ use_offline = offline or os.environ.get("PAW_OFFLINE", "").strip() in ("1", "true", "yes")
136
+
137
+ if is_pinned:
138
+ cached = get_cached_slug(program_id)
139
+ if cached:
140
+ resolved_id = cached
141
+ else:
142
+ status(f"Resolving {program_id}...")
143
+ from .client import PAWClient
144
+ client = PAWClient(api_url=get_api_url(), api_key=get_api_key())
145
+ resolved_id = client.resolve_slug(program_id)
146
+ save_slug_mapping(program_id, resolved_id)
147
+ elif use_offline:
148
+ cached = get_cached_slug(program_id)
149
+ if cached:
150
+ resolved_id = cached
151
+ else:
152
+ raise RuntimeError(
153
+ f"No cached version of '{program_id}'. Cannot resolve offline. "
154
+ "Run once with internet to populate the cache."
155
+ )
128
156
  else:
129
- status(f"Resolving {program_id}...")
130
157
  from .client import PAWClient
131
158
  client = PAWClient(api_url=get_api_url(), api_key=get_api_key())
132
- resolved_id = client.resolve_slug(program_id)
133
- save_slug_mapping(program_id, resolved_id)
159
+ try:
160
+ status(f"Resolving {program_id}...")
161
+ resolved_id = client.resolve_slug(program_id)
162
+ save_slug_mapping(program_id, resolved_id)
163
+ except Exception:
164
+ cached = get_cached_slug(program_id)
165
+ if cached:
166
+ status(f"Warning: could not reach server, using cached version of {program_id}")
167
+ resolved_id = cached
168
+ else:
169
+ raise
134
170
 
135
171
  if not is_program_cached(resolved_id):
136
172
  from .client import PAWClient
@@ -221,6 +257,25 @@ def compile_and_load(
221
257
  return function(program, n_ctx=n_ctx, n_gpu_layers=n_gpu_layers, verbose=verbose)
222
258
 
223
259
 
260
+ def list_versions(slug: str) -> dict:
261
+ """List all versions of a named program (slug).
262
+
263
+ Args:
264
+ slug: Slug in ``username/slug-name`` or bare ``slug-name`` format.
265
+
266
+ Returns:
267
+ Dict with ``slug``, ``main_version``, and ``versions`` list.
268
+
269
+ Example:
270
+ >>> versions = paw.list_versions("da03/bibtex-normalizer")
271
+ >>> for v in versions["versions"]:
272
+ ... print(f"v{v['version']}: {v['program_id'][:12]} {'(main)' if v['is_main'] else ''}")
273
+ """
274
+ from .client import PAWClient
275
+ client = PAWClient(api_url=get_api_url(), api_key=get_api_key())
276
+ return client.list_slug_versions(slug)
277
+
278
+
224
279
  def list_programs(sort: str = "recent", per_page: int = 20, page: int = 1) -> dict:
225
280
  """List your compiled programs. Requires authentication (PAW_API_KEY).
226
281
 
@@ -242,6 +297,7 @@ __all__ = [
242
297
  "compile_and_load",
243
298
  "function",
244
299
  "list_programs",
300
+ "list_versions",
245
301
  "login",
246
302
  "get_api_url",
247
303
  "get_api_key",
@@ -27,6 +27,8 @@ class Program:
27
27
  compiler_snapshot: Optional[str] = None
28
28
  timings: Optional[dict] = None
29
29
  error: Optional[str] = None
30
+ version: Optional[int] = None
31
+ version_action: Optional[str] = None
30
32
 
31
33
 
32
34
  class PAWClient:
@@ -92,6 +94,8 @@ class PAWClient:
92
94
  compiler_snapshot=data.get("compiler_snapshot"),
93
95
  timings=data.get("timings"),
94
96
  error=data.get("error"),
97
+ version=data.get("version"),
98
+ version_action=data.get("version_action"),
95
99
  )
96
100
 
97
101
  def resolve_slug(self, slug: str) -> str:
@@ -182,6 +186,16 @@ class PAWClient:
182
186
  resp.raise_for_status()
183
187
  return resp.json()
184
188
 
189
+ def list_slug_versions(self, slug: str) -> dict:
190
+ """List all versions of a slug. Slug format: 'username/slug-name' or bare 'slug-name'."""
191
+ resp = httpx.get(
192
+ f"{self._api_url}/api/v1/programs/{slug}/versions",
193
+ headers=self._headers(),
194
+ timeout=10.0,
195
+ )
196
+ resp.raise_for_status()
197
+ return resp.json()
198
+
185
199
  def list_programs(self, sort: str = "recent", per_page: int = 20, page: int = 1) -> dict:
186
200
  """List programs for the authenticated user."""
187
201
  resp = httpx.get(
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "programasweights"
7
- version = "0.2.8"
7
+ version = "0.3.0"
8
8
  description = "Compile natural language specifications into neural programs that run locally via llama.cpp."
9
9
  readme = "PYPI_README.md"
10
10
  requires-python = ">=3.9"