Skip to content

Commit 3b5cc15

Browse files
sethjuarezCopilot
andcommitted
fix(python): resolve ruff lint/format errors, add RELEASING.md
- Fix f-string and unused variable warnings in release script - Format all Python files to pass ruff format --check - Add RELEASING.md documenting release process for both runtimes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a9ee45e commit 3b5cc15

File tree

11 files changed

+362
-262
lines changed

11 files changed

+362
-262
lines changed

RELEASING.md

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Releasing Prompty
2+
3+
This document describes how to publish new versions of the Prompty runtimes.
4+
5+
## Overview
6+
7+
Both runtimes use **tag-based releases**: you run a local script that bumps
8+
versions, commits, tags, and pushes. The tag triggers a CI workflow that
9+
builds, tests, and publishes to the package registry via OIDC (no secrets needed).
10+
11+
| Runtime | Registry | Tag format | Script |
12+
|---------|----------|------------|--------|
13+
| Python | [PyPI](https://pypi.org/project/prompty/) | `python/{version}` | `python scripts/release.py` |
14+
| TypeScript | [npm](https://www.npmjs.com/package/@prompty/core) | `typescript/{version}` | `npm run release` |
15+
16+
## Python
17+
18+
### Prerequisites
19+
20+
- Push access to `microsoft/prompty`
21+
- [PyPI trusted publisher](https://docs.pypi.org/trusted-publishers/) configured for `prompty`
22+
23+
### Version format
24+
25+
Python uses [PEP 440](https://peps.python.org/pep-0440/):
26+
`2.0.0a1` (alpha), `2.0.0b1` (beta), `2.0.0rc1` (release candidate), `2.0.0` (stable)
27+
28+
### Publishing
29+
30+
```bash
31+
cd runtime/python/prompty
32+
33+
# Preview what will happen
34+
python scripts/release.py --dry-run
35+
36+
# Bump prerelease: 2.0.0a1 → 2.0.0a2
37+
python scripts/release.py
38+
39+
# Bump minor: 2.0.0a2 → 2.1.0a1
40+
python scripts/release.py --bump minor
41+
42+
# Bump major: 2.1.0a1 → 3.0.0a1
43+
python scripts/release.py --bump major
44+
45+
# Set explicit version (e.g., exit prerelease)
46+
python scripts/release.py --version 2.0.0
47+
```
48+
49+
The script:
50+
1. Updates `prompty/_version.py`
51+
2. Commits: `chore(python): release v{version}`
52+
3. Tags: `python/{version}`
53+
4. Pushes commit + tag to origin
54+
55+
CI (`prompty-python.yml`) then:
56+
1. Tests on Python 3.11/3.12/3.13 × ubuntu/macOS/windows (9 jobs)
57+
2. Builds wheel + sdist with flit
58+
3. Publishes to PyPI via OIDC trusted publisher
59+
60+
### Verify
61+
62+
```bash
63+
pip install prompty==2.0.0a2
64+
python -c "import prompty; print(prompty.__version__)"
65+
```
66+
67+
## TypeScript
68+
69+
### Prerequisites
70+
71+
- Push access to `microsoft/prompty`
72+
- [npm trusted publishers](https://docs.npmjs.com/trusted-publishers) configured for
73+
`@prompty/core`, `@prompty/openai`, and `@prompty/foundry`
74+
75+
### Version format
76+
77+
TypeScript uses [semver](https://semver.org/) with prerelease:
78+
`2.0.0-alpha.1`, `2.0.0-beta.1`, `2.0.0-rc.1`, `2.0.0`
79+
80+
### Publishing
81+
82+
```bash
83+
cd runtime/typescript
84+
85+
# Preview what will happen
86+
npm run release -- --dry-run
87+
88+
# Bump prerelease: 2.0.0-alpha.1 → 2.0.0-alpha.2
89+
npm run release
90+
91+
# Bump minor: 2.0.0-alpha.2 → 2.1.0-alpha.1
92+
npm run release -- --bump minor
93+
94+
# Bump major: 2.1.0-alpha.1 → 3.0.0-alpha.1
95+
npm run release -- --bump major
96+
97+
# Set explicit version (e.g., exit prerelease)
98+
npm run release -- --version 2.0.0
99+
```
100+
101+
The script:
102+
1. Updates all 4 `packages/*/package.json` (linked versions)
103+
2. Updates cross-package `@prompty/*` dependency references
104+
3. Commits: `chore(typescript): release v{version}`
105+
4. Tags: `typescript/{version}`
106+
5. Pushes commit + tag to origin
107+
108+
CI (`prompty-ts-release.yml`) then:
109+
1. Installs, builds, and tests
110+
2. Auto-detects npm dist-tag (`alpha` for prereleases, `latest` for stable)
111+
3. Publishes `@prompty/core`, `@prompty/openai`, `@prompty/foundry` via OIDC
112+
113+
### Verify
114+
115+
```bash
116+
npm info @prompty/core versions --json | tail -5
117+
```
118+
119+
## Packages published
120+
121+
### Python (single package with extras)
122+
123+
| Install | What you get |
124+
|---------|-------------|
125+
| `pip install prompty` | Core only |
126+
| `pip install prompty[openai]` | + OpenAI provider |
127+
| `pip install prompty[foundry]` | + Microsoft Foundry provider |
128+
| `pip install prompty[jinja2]` | + Jinja2 renderer |
129+
| `pip install prompty[all]` | Everything |
130+
131+
### TypeScript (separate packages)
132+
133+
| Package | What it is |
134+
|---------|-----------|
135+
| `@prompty/core` | Loader, pipeline, types, tracing |
136+
| `@prompty/openai` | OpenAI provider |
137+
| `@prompty/foundry` | Microsoft Foundry provider |
138+
139+
## Troubleshooting
140+
141+
### OIDC publish fails with 403
142+
143+
The trusted publisher config on the registry must match exactly:
144+
- **Owner**: `microsoft`
145+
- **Repository**: `prompty`
146+
- **Workflow filename**: `prompty-python.yml` (Python) or `prompty-ts-release.yml` (TypeScript)
147+
- **Environment**: *(leave blank)*
148+
149+
### Tests pass locally but fail in CI
150+
151+
The CI runs `ruff check .` and `ruff format --check .` before tests.
152+
Always run both locally before releasing:
153+
154+
```bash
155+
# Python
156+
cd runtime/python/prompty
157+
ruff check . && ruff format --check . && python -m pytest tests/ -q
158+
159+
# TypeScript
160+
cd runtime/typescript
161+
npm run build && npm run test
162+
```

runtime/python/prompty/prompty/model/_Connection.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,8 @@ def load_kind(data: dict, context: LoadContext | None) -> "Connection":
8282
return OAuthConnection.load(data, context)
8383

8484
else:
85-
raise ValueError(
86-
f"Unknown Connection discriminator value: {discriminator_value}"
87-
)
85+
raise ValueError(f"Unknown Connection discriminator value: {discriminator_value}")
8886
else:
89-
9087
raise ValueError("Missing Connection discriminator property: 'kind'")
9188

9289
def save(self, context: SaveContext | None = None) -> dict[str, Any]:

runtime/python/prompty/prompty/model/_Prompty.py

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,7 @@ def load_inputs(data: dict | list, context: LoadContext | None) -> list[Property
120120
return [Property.load(item, context) for item in data]
121121

122122
@staticmethod
123-
def save_inputs(
124-
items: list[Property], context: SaveContext | None
125-
) -> dict[str, Any] | list[dict[str, Any]]:
123+
def save_inputs(items: list[Property], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]:
126124
if context is None:
127125
context = SaveContext()
128126

@@ -138,11 +136,7 @@ def save_inputs(
138136
# Check if we can use shorthand (only primary property set)
139137
if context.use_shorthand and hasattr(item, "_shorthand_property"):
140138
shorthand_prop = item._shorthand_property
141-
if (
142-
shorthand_prop
143-
and len(item_data) == 1
144-
and shorthand_prop in item_data
145-
):
139+
if shorthand_prop and len(item_data) == 1 and shorthand_prop in item_data:
146140
result[name] = item_data[shorthand_prop]
147141
continue
148142
result[name] = item_data
@@ -169,9 +163,7 @@ def load_outputs(data: dict | list, context: LoadContext | None) -> list[Propert
169163
return [Property.load(item, context) for item in data]
170164

171165
@staticmethod
172-
def save_outputs(
173-
items: list[Property], context: SaveContext | None
174-
) -> dict[str, Any] | list[dict[str, Any]]:
166+
def save_outputs(items: list[Property], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]:
175167
if context is None:
176168
context = SaveContext()
177169

@@ -187,11 +179,7 @@ def save_outputs(
187179
# Check if we can use shorthand (only primary property set)
188180
if context.use_shorthand and hasattr(item, "_shorthand_property"):
189181
shorthand_prop = item._shorthand_property
190-
if (
191-
shorthand_prop
192-
and len(item_data) == 1
193-
and shorthand_prop in item_data
194-
):
182+
if shorthand_prop and len(item_data) == 1 and shorthand_prop in item_data:
195183
result[name] = item_data[shorthand_prop]
196184
continue
197185
result[name] = item_data
@@ -218,9 +206,7 @@ def load_tools(data: dict | list, context: LoadContext | None) -> list[Tool]:
218206
return [Tool.load(item, context) for item in data]
219207

220208
@staticmethod
221-
def save_tools(
222-
items: list[Tool], context: SaveContext | None
223-
) -> dict[str, Any] | list[dict[str, Any]]:
209+
def save_tools(items: list[Tool], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]:
224210
if context is None:
225211
context = SaveContext()
226212

@@ -236,11 +222,7 @@ def save_tools(
236222
# Check if we can use shorthand (only primary property set)
237223
if context.use_shorthand and hasattr(item, "_shorthand_property"):
238224
shorthand_prop = item._shorthand_property
239-
if (
240-
shorthand_prop
241-
and len(item_data) == 1
242-
and shorthand_prop in item_data
243-
):
225+
if shorthand_prop and len(item_data) == 1 and shorthand_prop in item_data:
244226
result[name] = item_data[shorthand_prop]
245227
continue
246228
result[name] = item_data

runtime/python/prompty/prompty/model/_Property.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,10 @@ def load_kind(data: dict, context: LoadContext | None) -> "Property":
106106
return ObjectProperty.load(data, context)
107107

108108
else:
109-
110109
# create new instance (stop recursion)
111110
return Property()
112111

113112
else:
114-
115113
# create new instance
116114
return Property()
117115

@@ -309,17 +307,13 @@ def load(data: Any, context: LoadContext | None = None) -> "ObjectProperty":
309307
if data is not None and "kind" in data:
310308
instance.kind = data["kind"]
311309
if data is not None and "properties" in data:
312-
instance.properties = ObjectProperty.load_properties(
313-
data["properties"], context
314-
)
310+
instance.properties = ObjectProperty.load_properties(data["properties"], context)
315311
if context is not None:
316312
instance = context.process_output(instance)
317313
return instance
318314

319315
@staticmethod
320-
def load_properties(
321-
data: dict | list, context: LoadContext | None
322-
) -> list[Property]:
316+
def load_properties(data: dict | list, context: LoadContext | None) -> list[Property]:
323317
if isinstance(data, dict):
324318
result: list[Property] = []
325319
for k, v in data.items():
@@ -334,9 +328,7 @@ def load_properties(
334328
return [Property.load(item, context) for item in data]
335329

336330
@staticmethod
337-
def save_properties(
338-
items: list[Property], context: SaveContext | None
339-
) -> dict[str, Any] | list[dict[str, Any]]:
331+
def save_properties(items: list[Property], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]:
340332
if context is None:
341333
context = SaveContext()
342334

@@ -352,11 +344,7 @@ def save_properties(
352344
# Check if we can use shorthand (only primary property set)
353345
if context.use_shorthand and hasattr(item, "_shorthand_property"):
354346
shorthand_prop = item._shorthand_property
355-
if (
356-
shorthand_prop
357-
and len(item_data) == 1
358-
and shorthand_prop in item_data
359-
):
347+
if shorthand_prop and len(item_data) == 1 and shorthand_prop in item_data:
360348
result[name] = item_data[shorthand_prop]
361349
continue
362350
result[name] = item_data
@@ -385,9 +373,7 @@ def save(self, context: SaveContext | None = None) -> dict[str, Any]:
385373
if obj.kind is not None:
386374
result["kind"] = obj.kind
387375
if obj.properties is not None:
388-
result["properties"] = ObjectProperty.save_properties(
389-
obj.properties, context
390-
)
376+
result["properties"] = ObjectProperty.save_properties(obj.properties, context)
391377

392378
return result
393379

runtime/python/prompty/prompty/model/_Tool.py

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,7 @@ def load_bindings(data: dict | list, context: LoadContext | None) -> list[Bindin
8686
return [Binding.load(item, context) for item in data]
8787

8888
@staticmethod
89-
def save_bindings(
90-
items: list[Binding], context: SaveContext | None
91-
) -> dict[str, Any] | list[dict[str, Any]]:
89+
def save_bindings(items: list[Binding], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]:
9290
if context is None:
9391
context = SaveContext()
9492

@@ -104,11 +102,7 @@ def save_bindings(
104102
# Check if we can use shorthand (only primary property set)
105103
if context.use_shorthand and hasattr(item, "_shorthand_property"):
106104
shorthand_prop = item._shorthand_property
107-
if (
108-
shorthand_prop
109-
and len(item_data) == 1
110-
and shorthand_prop in item_data
111-
):
105+
if shorthand_prop and len(item_data) == 1 and shorthand_prop in item_data:
112106
result[name] = item_data[shorthand_prop]
113107
continue
114108
result[name] = item_data
@@ -132,12 +126,10 @@ def load_kind(data: dict, context: LoadContext | None) -> "Tool":
132126
return OpenApiTool.load(data, context)
133127

134128
else:
135-
136129
# load default instance
137130
return CustomTool.load(data, context)
138131

139132
else:
140-
141133
raise ValueError("Missing Tool discriminator property: 'kind'")
142134

143135
def save(self, context: SaveContext | None = None) -> dict[str, Any]:
@@ -236,19 +228,15 @@ def load(data: Any, context: LoadContext | None = None) -> "FunctionTool":
236228
if data is not None and "kind" in data:
237229
instance.kind = data["kind"]
238230
if data is not None and "parameters" in data:
239-
instance.parameters = FunctionTool.load_parameters(
240-
data["parameters"], context
241-
)
231+
instance.parameters = FunctionTool.load_parameters(data["parameters"], context)
242232
if data is not None and "strict" in data:
243233
instance.strict = data["strict"]
244234
if context is not None:
245235
instance = context.process_output(instance)
246236
return instance
247237

248238
@staticmethod
249-
def load_parameters(
250-
data: dict | list, context: LoadContext | None
251-
) -> list[Property]:
239+
def load_parameters(data: dict | list, context: LoadContext | None) -> list[Property]:
252240
if isinstance(data, dict):
253241
result: list[Property] = []
254242
for k, v in data.items():
@@ -263,9 +251,7 @@ def load_parameters(
263251
return [Property.load(item, context) for item in data]
264252

265253
@staticmethod
266-
def save_parameters(
267-
items: list[Property], context: SaveContext | None
268-
) -> dict[str, Any] | list[dict[str, Any]]:
254+
def save_parameters(items: list[Property], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]:
269255
if context is None:
270256
context = SaveContext()
271257

@@ -281,11 +267,7 @@ def save_parameters(
281267
# Check if we can use shorthand (only primary property set)
282268
if context.use_shorthand and hasattr(item, "_shorthand_property"):
283269
shorthand_prop = item._shorthand_property
284-
if (
285-
shorthand_prop
286-
and len(item_data) == 1
287-
and shorthand_prop in item_data
288-
):
270+
if shorthand_prop and len(item_data) == 1 and shorthand_prop in item_data:
289271
result[name] = item_data[shorthand_prop]
290272
continue
291273
result[name] = item_data

0 commit comments

Comments
 (0)