positronic-model-server 0.2.2__tar.gz → 0.2.3__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 (16) hide show
  1. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/PKG-INFO +12 -9
  2. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/README.md +11 -8
  3. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/positronic_model_server/spec.py +7 -5
  4. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/positronic_model_server/tests/test_spec.py +28 -2
  5. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/pyproject.toml +1 -1
  6. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/.gitignore +0 -0
  7. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/positronic_model_server/grpc_wire.py +0 -0
  8. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/positronic_model_server/keys.py +0 -0
  9. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/positronic_model_server/protocol.py +0 -0
  10. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/positronic_model_server/py.typed +0 -0
  11. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/positronic_model_server/serialization.py +0 -0
  12. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/positronic_model_server/server.py +0 -0
  13. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/positronic_model_server/server_wire.py +0 -0
  14. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/positronic_model_server/tests/test_model_server.py +0 -0
  15. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/positronic_model_server/tests/test_serialization.py +0 -0
  16. {positronic_model_server-0.2.2 → positronic_model_server-0.2.3}/positronic_model_server/websocket_wire.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: positronic-model-server
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Model serving, session lifecycle and serialization without Positronic dependencies
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
@@ -22,9 +22,9 @@ The wrapper owns transport, sessions, serialization and cleanup. Vendor scripts
22
22
  model directly in the same process and describe the client pipeline as plain data.
23
23
 
24
24
  ```bash
25
- uv pip install "positronic-model-server==0.2.2"
26
- uv pip install "positronic-model-server[websocket]==0.2.2"
27
- uv pip install "positronic-model-server[grpc]==0.2.2"
25
+ uv pip install "positronic-model-server==0.2.3"
26
+ uv pip install "positronic-model-server[websocket]==0.2.3"
27
+ uv pip install "positronic-model-server[grpc]==0.2.3"
28
28
  ```
29
29
 
30
30
  The core depends on NumPy, Pillow, msgpack and `positronic-wire`'s dependency-free transport
@@ -45,7 +45,7 @@ from positronic_model_server.websocket_wire import WebsocketWire
45
45
 
46
46
  class EchoSession(Session):
47
47
  def __init__(self, fps):
48
- super().__init__(component("chunked_schedule", version=2, fps=fps))
48
+ super().__init__(component("chunked_schedule", fps=fps))
49
49
 
50
50
  def infer(self, observation):
51
51
  return [observation]
@@ -137,7 +137,7 @@ and instantiates their implementations:
137
137
  from positronic_model_server.spec import component, parallel, sequence
138
138
 
139
139
  description = sequence(
140
- component("chunked_schedule", version=2, fps=20),
140
+ component("chunked_schedule", fps=20),
141
141
  parallel(
142
142
  component("restrict_image_size", width=320, height=180),
143
143
  component("metadata", values={"model": "example"}),
@@ -145,9 +145,12 @@ description = sequence(
145
145
  )
146
146
  ```
147
147
 
148
- `component` accepts a name, explicit version and keyword arguments, normalizing arguments through
149
- JSON. `sequence` and `parallel` reject empty compositions. Component names and argument meanings
150
- belong to the client registry; these helpers do not resolve classes or validate model parameters.
148
+ `component` accepts a name and JSON-compatible keyword arguments. By default it writes `version="latest"`,
149
+ which selects the client's latest supported version. This can change after a client upgrade;
150
+ pass `version=2`, for example, to pin a specific version. Legacy descriptions without a version mean v1.
151
+
152
+ `sequence` and `parallel` reject empty compositions. The helpers check description structure;
153
+ the client checks component names, supported versions and arguments when it builds the pipeline.
151
154
  The structural keys (`NAME`, `VERSION`, `ARGS`, `SEQ`, `PAR`) have one definition in `spec`.
152
155
 
153
156
  The wrapper defines only constants used by its own production code. Vendor settings belong to small
@@ -5,9 +5,9 @@ The wrapper owns transport, sessions, serialization and cleanup. Vendor scripts
5
5
  model directly in the same process and describe the client pipeline as plain data.
6
6
 
7
7
  ```bash
8
- uv pip install "positronic-model-server==0.2.2"
9
- uv pip install "positronic-model-server[websocket]==0.2.2"
10
- uv pip install "positronic-model-server[grpc]==0.2.2"
8
+ uv pip install "positronic-model-server==0.2.3"
9
+ uv pip install "positronic-model-server[websocket]==0.2.3"
10
+ uv pip install "positronic-model-server[grpc]==0.2.3"
11
11
  ```
12
12
 
13
13
  The core depends on NumPy, Pillow, msgpack and `positronic-wire`'s dependency-free transport
@@ -28,7 +28,7 @@ from positronic_model_server.websocket_wire import WebsocketWire
28
28
 
29
29
  class EchoSession(Session):
30
30
  def __init__(self, fps):
31
- super().__init__(component("chunked_schedule", version=2, fps=fps))
31
+ super().__init__(component("chunked_schedule", fps=fps))
32
32
 
33
33
  def infer(self, observation):
34
34
  return [observation]
@@ -120,7 +120,7 @@ and instantiates their implementations:
120
120
  from positronic_model_server.spec import component, parallel, sequence
121
121
 
122
122
  description = sequence(
123
- component("chunked_schedule", version=2, fps=20),
123
+ component("chunked_schedule", fps=20),
124
124
  parallel(
125
125
  component("restrict_image_size", width=320, height=180),
126
126
  component("metadata", values={"model": "example"}),
@@ -128,9 +128,12 @@ description = sequence(
128
128
  )
129
129
  ```
130
130
 
131
- `component` accepts a name, explicit version and keyword arguments, normalizing arguments through
132
- JSON. `sequence` and `parallel` reject empty compositions. Component names and argument meanings
133
- belong to the client registry; these helpers do not resolve classes or validate model parameters.
131
+ `component` accepts a name and JSON-compatible keyword arguments. By default it writes `version="latest"`,
132
+ which selects the client's latest supported version. This can change after a client upgrade;
133
+ pass `version=2`, for example, to pin a specific version. Legacy descriptions without a version mean v1.
134
+
135
+ `sequence` and `parallel` reject empty compositions. The helpers check description structure;
136
+ the client checks component names, supported versions and arguments when it builds the pipeline.
134
137
  The structural keys (`NAME`, `VERSION`, `ARGS`, `SEQ`, `PAR`) have one definition in `spec`.
135
138
 
136
139
  The wrapper defines only constants used by its own production code. Vendor settings belong to small
@@ -2,7 +2,7 @@
2
2
 
3
3
  import json
4
4
  from collections.abc import Sequence
5
- from typing import Any
5
+ from typing import Any, Literal
6
6
 
7
7
  SEQ = 'seq'
8
8
  PAR = 'par'
@@ -10,13 +10,15 @@ NAME = 'name'
10
10
  ARGS = 'args'
11
11
  VERSION = 'version'
12
12
 
13
+ LATEST: Literal['latest'] = 'latest'
13
14
 
14
- def component(name: str, /, *, version: int = 1, **args: Any) -> dict[str, Any]:
15
- """Describe a component without importing it; arguments must survive a JSON round trip."""
15
+
16
+ def component(name: str, /, *, version: int | Literal['latest'] = LATEST, **args: Any) -> dict[str, Any]:
17
+ """Describe a component, requesting the client's latest supported version unless pinned."""
16
18
  if not isinstance(name, str) or not name:
17
19
  raise ValueError('A component needs a nonempty name')
18
- if type(version) is not int or version < 1:
19
- raise ValueError('A component version must be a positive integer')
20
+ if version != LATEST and (type(version) is not int or version < 1):
21
+ raise ValueError('A component version must be a positive integer or "latest"')
20
22
  # JSON normalizes tuples to lists and rejects Python objects and nonfinite numbers.
21
23
  return {NAME: name, VERSION: version, ARGS: json.loads(json.dumps(args, allow_nan=False))}
22
24
 
@@ -5,6 +5,7 @@ import json
5
5
  import pytest
6
6
  from positronic_model_server.spec import (
7
7
  ARGS,
8
+ LATEST,
8
9
  NAME,
9
10
  PAR,
10
11
  SEQ,
@@ -23,11 +24,29 @@ def test_composition_uses_the_client_wire_format():
23
24
  assert json.loads(json.dumps(spec)) == {
24
25
  'seq': [
25
26
  {'name': 'scheduler', 'version': 2, 'args': {'fps': 20}},
26
- {'par': [{'name': 'left', 'version': 1, 'args': {}}, {'name': 'right', 'version': 1, 'args': {}}]},
27
+ {
28
+ 'par': [
29
+ {'name': 'left', 'version': 'latest', 'args': {}},
30
+ {'name': 'right', 'version': 'latest', 'args': {}},
31
+ ]
32
+ },
27
33
  ]
28
34
  }
29
35
 
30
36
 
37
+ def test_omitted_versions_request_latest():
38
+ saved = json.loads(json.dumps(component('vendor-owned')))
39
+ validate(saved)
40
+ assert saved[VERSION] == LATEST
41
+
42
+
43
+ @pytest.mark.parametrize('version', [LATEST, 1, 17])
44
+ def test_explicit_versions_survive_json_and_validation(version):
45
+ saved = json.loads(json.dumps(component('vendor-owned', version=version)))
46
+ validate(saved)
47
+ assert saved[VERSION] == version
48
+
49
+
31
50
  def test_arguments_are_normalized_to_json():
32
51
  arguments = {'offsets': (-0.1, 0), 'nested': {'values': [1, None, True]}}
33
52
  spec = component('vendor-owned', **arguments)
@@ -42,7 +61,7 @@ def test_non_json_arguments_fail_at_construction(value):
42
61
  component('vendor-owned', value=value)
43
62
 
44
63
 
45
- @pytest.mark.parametrize('version', [0, -1, True, '1'])
64
+ @pytest.mark.parametrize('version', [0, -1, True, '1', 'newest', None])
46
65
  def test_invalid_versions_are_rejected(version):
47
66
  with pytest.raises(ValueError, match='positive integer'):
48
67
  component('vendor-owned', version=version)
@@ -62,6 +81,7 @@ def test_empty_composition_is_rejected(compose):
62
81
  {PAR: []},
63
82
  {SEQ: [component('x')], NAME: 'x'},
64
83
  {NAME: 'x', VERSION: False},
84
+ {NAME: 'chunked_schedule', VERSION: None},
65
85
  {NAME: 'x', ARGS: []},
66
86
  {NAME: 'x', ARGS: {'value': float('nan')}},
67
87
  ],
@@ -75,6 +95,12 @@ def test_structural_validation_does_not_need_a_component_registry():
75
95
  validate(sequence(component('unknown-vendor-component', version=17), parallel(component('another'))))
76
96
 
77
97
 
98
+ def test_validation_preserves_unversioned_wire_descriptions():
99
+ node = {NAME: 'chunked_schedule', ARGS: {'fps': 20}}
100
+ validate(node)
101
+ assert VERSION not in node
102
+
103
+
78
104
  def test_parameter_resolution_refuses_unknown_names_and_copies_defaults():
79
105
  defaults = {'options': {'steps': [1, 2]}, 'fps': 20}
80
106
  effective = resolve_params(defaults, {'fps': 10})
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "positronic-model-server"
3
- version = "0.2.2"
3
+ version = "0.2.3"
4
4
  description = "Model serving, session lifecycle and serialization without Positronic dependencies"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"