programasweights 0.4.6__tar.gz → 0.4.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 (74) hide show
  1. {programasweights-0.4.6 → programasweights-0.4.8}/CHANGELOG.md +16 -0
  2. {programasweights-0.4.6 → programasweights-0.4.8}/PKG-INFO +15 -1
  3. {programasweights-0.4.6 → programasweights-0.4.8}/PYPI_README.md +14 -0
  4. {programasweights-0.4.6 → programasweights-0.4.8}/README.md +14 -0
  5. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/__init__.py +64 -8
  6. programasweights-0.4.8/programasweights/_remote.py +99 -0
  7. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/cli.py +27 -10
  8. {programasweights-0.4.6 → programasweights-0.4.8}/pyproject.toml +11 -1
  9. programasweights-0.4.6/.github/workflows/test.yml +0 -82
  10. programasweights-0.4.6/.readthedocs.yaml +0 -13
  11. programasweights-0.4.6/AGENTS.md +0 -255
  12. programasweights-0.4.6/docs/adr/001-llama-cpp-over-pytorch.md +0 -21
  13. programasweights-0.4.6/docs/adr/002-q4_0-adapter-format.md +0 -32
  14. programasweights-0.4.6/docs/adr/003-single-spec-field.md +0 -26
  15. programasweights-0.4.6/docs/adr/004-compiler-naming.md +0 -32
  16. programasweights-0.4.6/docs/adr/005-vllm-hidden-states.md +0 -24
  17. programasweights-0.4.6/docs/adr/006-email-api-key-auth.md +0 -41
  18. programasweights-0.4.6/docs/advanced/adrs.md +0 -67
  19. programasweights-0.4.6/docs/advanced/architecture.md +0 -47
  20. programasweights-0.4.6/docs/api-reference/cli.md +0 -105
  21. programasweights-0.4.6/docs/api-reference/python-sdk.md +0 -332
  22. programasweights-0.4.6/docs/api-reference/rest-api.md +0 -180
  23. programasweights-0.4.6/docs/architecture.md +0 -38
  24. programasweights-0.4.6/docs/case-studies/alien-taboo.md +0 -117
  25. programasweights-0.4.6/docs/case-studies/log-monitoring.md +0 -132
  26. programasweights-0.4.6/docs/case-studies/semantic-search.md +0 -146
  27. programasweights-0.4.6/docs/case-studies/site-navigation.md +0 -127
  28. programasweights-0.4.6/docs/case-studies/tool-calling.md +0 -477
  29. programasweights-0.4.6/docs/getting-started/first-program.md +0 -77
  30. programasweights-0.4.6/docs/getting-started/installation.md +0 -54
  31. programasweights-0.4.6/docs/getting-started/naming-programs.md +0 -78
  32. programasweights-0.4.6/docs/guide/browser-inference.md +0 -141
  33. programasweights-0.4.6/docs/guide/how-it-works.md +0 -47
  34. programasweights-0.4.6/docs/guide/local-inference.md +0 -48
  35. programasweights-0.4.6/docs/guide/writing-good-specs.md +0 -11
  36. programasweights-0.4.6/docs/hub/browsing-programs.md +0 -37
  37. programasweights-0.4.6/docs/hub/feedback-cases.md +0 -34
  38. programasweights-0.4.6/docs/hub/publishing-programs.md +0 -31
  39. programasweights-0.4.6/docs/index.md +0 -82
  40. programasweights-0.4.6/docs/requirements.txt +0 -2
  41. programasweights-0.4.6/examples/flask_app.py +0 -52
  42. programasweights-0.4.6/examples/jupyter_notebook.py +0 -52
  43. programasweights-0.4.6/examples/langchain_integration.py +0 -52
  44. programasweights-0.4.6/examples/paw_monitor.py +0 -180
  45. programasweights-0.4.6/examples/replace_openai.py +0 -44
  46. programasweights-0.4.6/mkdocs.yml +0 -87
  47. programasweights-0.4.6/tests/test_api_errors.py +0 -222
  48. programasweights-0.4.6/tests/test_base_interpreter.py +0 -845
  49. programasweights-0.4.6/tests/test_cli_auth.py +0 -265
  50. programasweights-0.4.6/tests/test_compile_timeouts.py +0 -129
  51. programasweights-0.4.6/tests/test_desktop_sdk.py +0 -1221
  52. programasweights-0.4.6/tests/test_local_program.py +0 -676
  53. programasweights-0.4.6/tests/test_offline_cache.py +0 -97
  54. programasweights-0.4.6/tests/test_runtime_registry_sdk.py +0 -123
  55. programasweights-0.4.6/tests/test_sdk.py +0 -484
  56. programasweights-0.4.6/tests/test_sdk.sh +0 -89
  57. {programasweights-0.4.6 → programasweights-0.4.8}/.gitignore +0 -0
  58. {programasweights-0.4.6 → programasweights-0.4.8}/LICENSE +0 -0
  59. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/_output.py +0 -0
  60. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/_program_reference.py +0 -0
  61. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/artifacts.py +0 -0
  62. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/cache.py +0 -0
  63. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/client.py +0 -0
  64. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/compiler/__init__.py +0 -0
  65. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/compiler/dummy.py +0 -0
  66. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/config.py +0 -0
  67. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/convert_peft_to_paw.py +0 -0
  68. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/errors.py +0 -0
  69. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/local_program.py +0 -0
  70. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/paw_format.py +0 -0
  71. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/runtime/__init__.py +0 -0
  72. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/runtime/interpreter.py +0 -0
  73. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/runtime/interpreter_onnx.py +0 -0
  74. {programasweights-0.4.6 → programasweights-0.4.8}/programasweights/runtime_llamacpp.py +0 -0
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.8 (2026-09-20)
4
+
5
+ - Reduce the source distribution to the SDK and files needed to build and
6
+ document the package.
7
+
8
+ ## 0.4.7 (2026-09-20)
9
+
10
+ - Add `remote=True` to `paw.function` and `paw.compile_and_load`, plus
11
+ `paw run --remote`, for hosted inference without downloading model assets
12
+ or loading the local runtime. Local inference remains the default.
13
+ - Use server generation defaults unless explicitly overridden. Reuse HTTP
14
+ connections, support `with` and `.close()`, and preserve structured
15
+ `paw.APIError` details.
16
+ - Reject remote inference combined with offline mode or incompatible local
17
+ runtime options.
18
+
3
19
  ## 0.4.6 (2026-09-13)
4
20
 
5
21
  - Add an optional `logits_processor` argument to a compiled or base program
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: programasweights
3
- Version: 0.4.6
3
+ Version: 0.4.8
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
@@ -66,6 +66,19 @@ fn("I love this!") # "positive"
66
66
 
67
67
  If you specifically want the smaller browser-compatible runtime, pass `compiler="paw-4b-gpt2"`. Otherwise, omit `compiler` and let the server default decide.
68
68
 
69
+ ## Remote inference (optional)
70
+
71
+ Use the hosted API for fast inference in around 150 ms, without a local model download.
72
+
73
+ ```python
74
+ import programasweights as paw
75
+
76
+ with paw.function("email-triage", remote=True) as remote_fn:
77
+ print(remote_fn("Urgent: the server is down!"))
78
+ ```
79
+
80
+ For direct HTTP calls, see the [REST API reference](https://programasweights.readthedocs.io/en/latest/api-reference/rest-api/#post-infer).
81
+
69
82
  ## Current Public Compilers
70
83
 
71
84
 
@@ -189,6 +202,7 @@ Generate API keys at [programasweights.com/settings](https://programasweights.co
189
202
  paw compile --spec "Extract error lines from logs" --json
190
203
  paw run --program <program_id> --input "[ERROR] timeout" --json
191
204
  paw run --program <program_id> --input "[ERROR] timeout" --offline --json
205
+ paw run --program <program_id> --input "[ERROR] timeout" --remote --json
192
206
  paw login
193
207
  ```
194
208
 
@@ -35,6 +35,19 @@ fn("I love this!") # "positive"
35
35
 
36
36
  If you specifically want the smaller browser-compatible runtime, pass `compiler="paw-4b-gpt2"`. Otherwise, omit `compiler` and let the server default decide.
37
37
 
38
+ ## Remote inference (optional)
39
+
40
+ Use the hosted API for fast inference in around 150 ms, without a local model download.
41
+
42
+ ```python
43
+ import programasweights as paw
44
+
45
+ with paw.function("email-triage", remote=True) as remote_fn:
46
+ print(remote_fn("Urgent: the server is down!"))
47
+ ```
48
+
49
+ For direct HTTP calls, see the [REST API reference](https://programasweights.readthedocs.io/en/latest/api-reference/rest-api/#post-infer).
50
+
38
51
  ## Current Public Compilers
39
52
 
40
53
 
@@ -158,6 +171,7 @@ Generate API keys at [programasweights.com/settings](https://programasweights.co
158
171
  paw compile --spec "Extract error lines from logs" --json
159
172
  paw run --program <program_id> --input "[ERROR] timeout" --json
160
173
  paw run --program <program_id> --input "[ERROR] timeout" --offline --json
174
+ paw run --program <program_id> --input "[ERROR] timeout" --remote --json
161
175
  paw login
162
176
  ```
163
177
 
@@ -35,6 +35,19 @@ fn("I love this!") # "positive"
35
35
 
36
36
  If you specifically want the smaller browser-compatible runtime, pass `compiler="paw-4b-gpt2"`. Otherwise, omit `compiler` and let the server default decide.
37
37
 
38
+ ## Remote inference (optional)
39
+
40
+ Use the hosted API for fast inference in around 150 ms, without a local model download.
41
+
42
+ ```python
43
+ import programasweights as paw
44
+
45
+ with paw.function("email-triage", remote=True) as remote_fn:
46
+ print(remote_fn("Urgent: the server is down!"))
47
+ ```
48
+
49
+ For direct HTTP calls, see the [REST API reference](docs/api-reference/rest-api.md#post-infer).
50
+
38
51
  ## Current Public Compilers
39
52
 
40
53
 
@@ -159,6 +172,7 @@ Generate API keys at [programasweights.com/settings](https://programasweights.co
159
172
  paw compile --spec "Extract error lines from logs" --json
160
173
  paw run --program <program_id> --input "[ERROR] timeout" --json
161
174
  paw run --program <program_id> --input "[ERROR] timeout" --offline --json
175
+ paw run --program <program_id> --input "[ERROR] timeout" --remote --json
162
176
  paw login
163
177
  ```
164
178
 
@@ -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.4.6"
30
+ __version__ = "0.4.8"
31
31
 
32
32
  from ._output import ProgressCallback, ProgressEvent, report_progress
33
33
  from .cache import CachedProgram
@@ -390,6 +390,23 @@ def list_cached_programs() -> list[CachedProgram]:
390
390
  return _list_cached_programs()
391
391
 
392
392
 
393
+ def _validate_remote_options(
394
+ *,
395
+ n_ctx: int,
396
+ n_gpu_layers: int | None,
397
+ verbose: bool,
398
+ offline: bool,
399
+ interpreter: str | None = None,
400
+ ) -> None:
401
+ if offline:
402
+ raise ValueError("remote=True cannot be combined with offline mode.")
403
+ if (
404
+ n_ctx != 2048 or n_gpu_layers is not None
405
+ or verbose or interpreter is not None
406
+ ):
407
+ raise ValueError("Local runtime options cannot be used with remote=True.")
408
+
409
+
393
410
  def function(
394
411
  program_id,
395
412
  n_ctx: int = 2048,
@@ -397,13 +414,15 @@ def function(
397
414
  verbose: bool = False,
398
415
  offline: bool = False,
399
416
  *,
417
+ remote: bool = False,
400
418
  interpreter: str | None = None,
401
419
  ):
402
420
  """Load a compiled program, or explicitly load a bare base interpreter.
403
421
 
404
- Hub references download the .paw bundle on first use; local paths supply
405
- it directly. Required runtime metadata and base models may still download
406
- unless offline mode is enabled. Subsequent calls reuse validated caches.
422
+ For local inference, Hub references download the .paw bundle on first use;
423
+ local paths supply it directly. Required runtime metadata and base models
424
+ may still download unless offline mode is enabled. Subsequent calls reuse
425
+ validated caches.
407
426
 
408
427
  Args:
409
428
  program_id: Program ID (str), slug (``da03/my-program``), pinned version
@@ -418,12 +437,15 @@ def function(
418
437
  offline: Prohibit network access. Local bundles may be imported, but
419
438
  their runtime and base model must already be available locally.
420
439
  Also set via ``PAW_OFFLINE=1`` env var.
440
+ remote: Run hosted inference without downloading model assets. Accepts
441
+ program IDs, slugs, pinned versions, and Program objects. Cannot
442
+ be combined with offline mode or non-default local runtime options.
421
443
  interpreter: Advanced adapter-free mode. This is only valid when
422
444
  ``program_id`` is explicitly ``None``. Initially supported values
423
445
  are ``"Qwen/Qwen3-0.6B"`` and ``"gpt2"``.
424
446
 
425
447
  Returns:
426
- A callable ``PawFunction`` that takes an input string and returns output.
448
+ A callable that takes an input string and returns an output string.
427
449
 
428
450
  Example:
429
451
  >>> fn = paw.function("email-triage")
@@ -432,6 +454,8 @@ def function(
432
454
 
433
455
  >>> fn = paw.function("da03/my-program@v2") # pinned version
434
456
 
457
+ >>> fn = paw.function("email-triage", remote=True)
458
+
435
459
  >>> fn = paw.function("./classifier.paw") # local bundle
436
460
 
437
461
  >>> base = paw.function(None, interpreter="gpt2")
@@ -440,6 +464,22 @@ def function(
440
464
  from . import cache
441
465
 
442
466
  offline = _offline_requested(offline)
467
+ if remote:
468
+ _validate_remote_options(
469
+ n_ctx=n_ctx,
470
+ n_gpu_layers=n_gpu_layers,
471
+ verbose=verbose,
472
+ offline=offline,
473
+ interpreter=interpreter,
474
+ )
475
+ from ._program_reference import local_program_path
476
+
477
+ reference = _coerce_program_reference(program_id)
478
+ if not reference or local_program_path(reference) is not None:
479
+ raise ValueError("Remote inference requires a program ID or slug.")
480
+ from ._remote import load_remote_function
481
+
482
+ return load_remote_function(reference)
443
483
  if n_gpu_layers is None:
444
484
  n_gpu_layers = int(os.environ.get("PAW_GPU_LAYERS", "-1"))
445
485
 
@@ -567,9 +607,11 @@ def compile_and_load(
567
607
  n_ctx: int = 2048,
568
608
  n_gpu_layers: int | None = None,
569
609
  verbose: bool = False,
610
+ *,
611
+ remote: bool = False,
570
612
  **compile_kwargs,
571
613
  ):
572
- """Compile a spec and immediately load it for local inference.
614
+ """Compile a spec and load it for local or remote inference.
573
615
 
574
616
  Convenience wrapper that combines ``paw.compile()`` and ``paw.function()``
575
617
  into a single call.
@@ -581,18 +623,32 @@ def compile_and_load(
581
623
  n_ctx: Context window size for llama.cpp.
582
624
  n_gpu_layers: GPU layers (-1 = all, 0 = CPU only).
583
625
  verbose: Print llama.cpp debug output.
626
+ remote: Run hosted inference without downloading model assets.
584
627
  **compile_kwargs: Additional args passed to compile (slug, public, etc.)
585
628
 
586
629
  Returns:
587
- A callable ``PawFunction``.
630
+ A callable that takes an input string and returns output.
588
631
 
589
632
  Example:
590
633
  >>> fn = paw.compile_and_load("Classify sentiment as positive or negative")
591
634
  >>> fn("I love this!")
592
635
  'positive'
593
636
  """
637
+ if remote:
638
+ _validate_remote_options(
639
+ n_ctx=n_ctx,
640
+ n_gpu_layers=n_gpu_layers,
641
+ verbose=verbose,
642
+ offline=_offline_requested(False),
643
+ )
594
644
  program = compile(spec, compiler=compiler, **compile_kwargs)
595
- return function(program, n_ctx=n_ctx, n_gpu_layers=n_gpu_layers, verbose=verbose)
645
+ return function(
646
+ program,
647
+ n_ctx=n_ctx,
648
+ n_gpu_layers=n_gpu_layers,
649
+ verbose=verbose,
650
+ remote=remote,
651
+ )
596
652
 
597
653
 
598
654
  def list_versions(slug: str) -> dict:
@@ -0,0 +1,99 @@
1
+ from __future__ import annotations
2
+
3
+ import httpx
4
+
5
+ from .errors import raise_for_api_status
6
+
7
+
8
+ def _infer(
9
+ client: httpx.Client,
10
+ program_id: str,
11
+ input_text: str,
12
+ *,
13
+ max_tokens: int | None = None,
14
+ temperature: float | None = None,
15
+ ) -> str:
16
+ if not isinstance(input_text, str):
17
+ raise TypeError("input_text must be a string.")
18
+
19
+ payload: dict[str, object] = {"program_id": program_id, "input": input_text}
20
+ if max_tokens is not None:
21
+ payload["max_tokens"] = max_tokens
22
+ if temperature is not None:
23
+ payload["temperature"] = temperature
24
+
25
+ response = client.post("api/v1/infer", json=payload)
26
+ raise_for_api_status(response)
27
+ data = response.json()
28
+ if not isinstance(data, dict) or not isinstance(data.get("output"), str):
29
+ raise ValueError("PAW inference returned an invalid output.")
30
+ return data["output"]
31
+
32
+
33
+ class RemotePawFunction:
34
+ """A hosted program that owns its HTTP client."""
35
+
36
+ def __init__(self, client: httpx.Client, program_id: str):
37
+ self._client = client
38
+ self._program_id = program_id
39
+
40
+ def __call__(
41
+ self,
42
+ input_text: str,
43
+ max_tokens: int | None = None,
44
+ temperature: float | None = None,
45
+ logits_processor=None,
46
+ ) -> str:
47
+ if self._client.is_closed:
48
+ raise RuntimeError("This RemotePawFunction has been closed.")
49
+ if logits_processor is not None:
50
+ raise ValueError("logits_processor is only supported for local inference.")
51
+ return _infer(
52
+ self._client,
53
+ self._program_id,
54
+ input_text,
55
+ max_tokens=max_tokens,
56
+ temperature=temperature,
57
+ )
58
+
59
+ def close(self) -> None:
60
+ self._client.close()
61
+
62
+ def __enter__(self) -> RemotePawFunction:
63
+ return self
64
+
65
+ def __exit__(self, exc_type, exc, traceback) -> None:
66
+ self.close()
67
+
68
+
69
+ def load_remote_function(reference: str) -> RemotePawFunction:
70
+ from urllib.parse import quote
71
+
72
+ from .cache import is_program_id
73
+ from .config import get_api_key, get_api_url
74
+
75
+ headers = {}
76
+ api_key = get_api_key()
77
+ if api_key:
78
+ headers["X-API-Key"] = api_key
79
+ client = httpx.Client(
80
+ base_url=get_api_url().rstrip("/") + "/",
81
+ headers=headers,
82
+ timeout=60.0,
83
+ )
84
+ try:
85
+ program_id = reference
86
+ if not is_program_id(reference):
87
+ response = client.get(
88
+ "api/v1/programs/resolve/" + quote(reference, safe=""),
89
+ timeout=10.0,
90
+ )
91
+ raise_for_api_status(response)
92
+ data = response.json()
93
+ program_id = data.get("program_id") if isinstance(data, dict) else None
94
+ if not isinstance(program_id, str) or not is_program_id(program_id):
95
+ raise ValueError("PAW returned an invalid program ID.")
96
+ return RemotePawFunction(client, program_id)
97
+ except BaseException:
98
+ client.close()
99
+ raise
@@ -4,7 +4,7 @@ ProgramAsWeights CLI.
4
4
 
5
5
  Usage:
6
6
  paw compile --spec "..." Compile a spec on the server
7
- paw run --program <id> --input "..." Run a program locally
7
+ paw run --program <id> --input "..." Run locally (or add --remote)
8
8
  paw rename <program> <slug> Set or change a program's slug
9
9
  paw info <program> Show program metadata
10
10
  paw login [key] Save API key for authentication
@@ -85,6 +85,9 @@ def cmd_run(args):
85
85
  program = getattr(args, "program", None)
86
86
  interpreter = getattr(args, "interpreter", None)
87
87
  offline = bool(getattr(args, "offline", False))
88
+ remote = bool(getattr(args, "remote", False))
89
+ if remote and (base_mode or offline):
90
+ raise ValueError("--remote cannot be combined with --base or --offline.")
88
91
  if base_mode:
89
92
  if program is not None:
90
93
  raise ValueError("--base cannot be combined with --program.")
@@ -98,13 +101,24 @@ def cmd_run(args):
98
101
  "--interpreter is only valid together with --base."
99
102
  )
100
103
 
101
- fn = paw.function(
102
- None if base_mode else program,
103
- verbose=args.verbose,
104
- offline=offline,
105
- interpreter=interpreter,
104
+ load_kwargs = dict(
105
+ verbose=args.verbose, offline=offline, interpreter=interpreter,
106
106
  )
107
- result = fn(args.input, max_tokens=args.max_tokens, temperature=args.temperature)
107
+ if remote:
108
+ load_kwargs["remote"] = True
109
+ max_tokens = args.max_tokens
110
+ temperature = args.temperature
111
+ if not remote:
112
+ if max_tokens is None:
113
+ max_tokens = 512
114
+ if temperature is None:
115
+ temperature = 0.0
116
+ fn = paw.function(None if base_mode else program, **load_kwargs)
117
+ try:
118
+ result = fn(args.input, max_tokens=max_tokens, temperature=temperature)
119
+ finally:
120
+ if remote:
121
+ fn.close()
108
122
 
109
123
  if args.json:
110
124
  print(
@@ -204,7 +218,7 @@ def main():
204
218
  p.add_argument("--private", action="store_true", help="Make program private (not listed on hub)")
205
219
  p.add_argument("--json", action="store_true", help="JSON output")
206
220
 
207
- p = sub.add_parser("run", help="Run a program locally via llama.cpp")
221
+ p = sub.add_parser("run", help="Run a program locally or remotely")
208
222
  run_mode = p.add_mutually_exclusive_group(required=True)
209
223
  run_mode.add_argument(
210
224
  "--program",
@@ -222,8 +236,9 @@ def main():
222
236
  help="Base interpreter; only valid with --base",
223
237
  )
224
238
  p.add_argument("--input", required=True, help="Input text")
225
- p.add_argument("--max-tokens", type=int, default=512)
226
- p.add_argument("--temperature", type=float, default=0.0)
239
+ p.add_argument("--max-tokens", type=int, default=None)
240
+ p.add_argument("--temperature", type=float, default=None)
241
+ p.add_argument("--remote", action="store_true", help="Use hosted inference")
227
242
  p.add_argument("--verbose", action="store_true")
228
243
  p.add_argument(
229
244
  "--offline",
@@ -250,6 +265,8 @@ def main():
250
265
  parser.print_help()
251
266
  return 0
252
267
  if args.command == "run":
268
+ if args.remote and (args.base or args.offline):
269
+ parser.error("--remote cannot be combined with --base or --offline")
253
270
  if args.base and args.interpreter is None:
254
271
  parser.error("paw run --base requires --interpreter")
255
272
  if args.program is not None and args.interpreter is not None:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "programasweights"
7
- version = "0.4.6"
7
+ version = "0.4.8"
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"
@@ -47,6 +47,16 @@ paw = "programasweights.cli:main"
47
47
  [tool.hatch.build.targets.wheel]
48
48
  packages = ["programasweights"]
49
49
 
50
+ [tool.hatch.build.targets.sdist]
51
+ only-include = [
52
+ "programasweights",
53
+ "README.md",
54
+ "PYPI_README.md",
55
+ "LICENSE",
56
+ "CHANGELOG.md",
57
+ "pyproject.toml",
58
+ ]
59
+
50
60
  [tool.pytest.ini_options]
51
61
  addopts = "-q"
52
62
  pythonpath = ["."]
@@ -1,82 +0,0 @@
1
- name: tests
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- pull_request:
7
-
8
- jobs:
9
- test:
10
- runs-on: ubuntu-latest
11
- strategy:
12
- fail-fast: false
13
- matrix:
14
- python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
15
- steps:
16
- - uses: actions/checkout@v4
17
-
18
- - name: Set up Python ${{ matrix.python-version }}
19
- uses: actions/setup-python@v5
20
- with:
21
- python-version: ${{ matrix.python-version }}
22
-
23
- - name: Install (hermetic deps only)
24
- # Install httpx + pytest and the package itself without pulling the heavy
25
- # llama-cpp-python build. Runtime tests inject a fake llama_cpp module,
26
- # so CI needs neither the native extension nor a model download.
27
- run: |
28
- python -m pip install --upgrade pip
29
- python -m pip install httpx pytest
30
- python -m pip install -e . --no-deps
31
-
32
- - name: Run hermetic tests
33
- # Scoped to tests that need no network, no model download, and no
34
- # PAW_API_KEY. Auth tests (@needs_auth) auto-skip without a key; the
35
- # network/model-download tests in test_sdk.py are excluded here and can
36
- # be run separately against a live server.
37
- run: |
38
- pytest \
39
- tests/test_api_errors.py \
40
- tests/test_compile_timeouts.py \
41
- tests/test_local_program.py \
42
- tests/test_base_interpreter.py \
43
- tests/test_cli_auth.py \
44
- tests/test_desktop_sdk.py \
45
- tests/test_runtime_registry_sdk.py \
46
- tests/test_sdk.py::TestInstallAndImport
47
-
48
- local-files-windows:
49
- runs-on: windows-latest
50
- strategy:
51
- fail-fast: false
52
- matrix:
53
- python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
54
- steps:
55
- - uses: actions/checkout@v4
56
- - uses: actions/setup-python@v5
57
- with:
58
- python-version: ${{ matrix.python-version }}
59
- - name: Install hermetic test dependencies
60
- run: |
61
- python -m pip install httpx pytest
62
- python -m pip install -e . --no-deps
63
- - name: Test Windows local paths, cache locks, and compile errors
64
- run: python -m pytest tests/test_api_errors.py tests/test_compile_timeouts.py tests/test_local_program.py --junitxml=test-results.xml
65
- - name: Annotate Windows test failures
66
- if: failure()
67
- shell: python
68
- run: |
69
- from pathlib import Path
70
- import xml.etree.ElementTree as ET
71
-
72
- report = Path("test-results.xml")
73
- if report.exists():
74
- for case in ET.parse(report).iter("testcase"):
75
- for result in case:
76
- if result.tag in {"failure", "error"}:
77
- # GitHub truncates annotations, so retain the actual
78
- # exception at the end of long pytest tracebacks.
79
- detail = result.text or result.get("message", "")
80
- message = f"{case.get('name')}: {detail[-3500:]}"
81
- message = message.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
82
- print(f"::error::{message}")
@@ -1,13 +0,0 @@
1
- version: 2
2
-
3
- build:
4
- os: ubuntu-24.04
5
- tools:
6
- python: "3.12"
7
-
8
- mkdocs:
9
- configuration: mkdocs.yml
10
-
11
- python:
12
- install:
13
- - requirements: docs/requirements.txt