Skip to content

Commit 2b48e48

Browse files
authored
Merge pull request #874 from atlanhq/bump-to-release-9.3.1
Bump to release `9.3.1`
2 parents 01a8495 + 9a6024c commit 2b48e48

7 files changed

Lines changed: 111 additions & 13 deletions

File tree

.claude/skills/release/SKILL.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,33 @@ Confirm the new version with the user before proceeding.
3434

3535
### 2. Gather changes since last release
3636

37-
Run:
37+
**Always gather from `origin/main`** — that's where PRs are merged. The current working branch is irrelevant here.
38+
39+
Step 1 — find the last release commit on main and list everything since it:
40+
```bash
41+
git fetch origin main --quiet
42+
git log --oneline $(git log --oneline origin/main | grep "\[release\]" | head -1 | awk '{print $1}')..origin/main
43+
```
44+
45+
Step 2 — extract PR numbers from merge commits and fetch details for each:
3846
```bash
39-
git log --oneline $(git log --oneline | grep "\[release\]" | head -1 | awk '{print $1}')..HEAD
47+
git log --oneline $(git log --oneline origin/main | grep "\[release\]" | head -1 | awk '{print $1}')..origin/main | grep "Merge pull request" | grep -oE '#[0-9]+' | tr -d '#'
4048
```
4149

42-
This finds commits since the last `[release]` commit. If none exist, use all commits on the current branch.
50+
Then for each PR number, run:
51+
```bash
52+
gh pr view <number> --json title,labels,body,mergedAt
53+
```
4354

44-
Also check merged PRs for additional context:
55+
If `gh` is not authenticated, fall back to the commit subjects alone — do NOT skip this step or silently use an empty list. Note any PRs you couldn't fetch so the user knows the notes may be incomplete.
56+
57+
Step 3 — also run this to catch non-merge commits (direct pushes, fixups, etc.):
4558
```bash
46-
gh pr list --state merged --base main --limit 30 --json title,labels,number,mergedAt
59+
git log --oneline --no-merges $(git log --oneline origin/main | grep "\[release\]" | head -1 | awk '{print $1}')..origin/main
4760
```
4861

62+
Use all of this — PR titles, PR bodies, labels, and direct commit messages — to write the release notes.
63+
4964
---
5065

5166
### 3. Draft release notes

HISTORY.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## 9.3.1 (March 23, 2026)
2+
3+
### Experimental: `pyatlan_v9`
4+
5+
- **`typedef` typing corrections and serialization fixes**: Corrected field types in `RelationshipDef` and removed hardcoded defaults (PR #867). Optional typedef fields now use `msgspec.UNSET` so they are omitted from serialization rather than emitted as `null` (PR #870). `AttributeDef.default_value` and `index_type_es_fields` relaxed to `Any` to accommodate varied server responses (PR #872). `is_rich_text` and `custom_metadata_version` now default to `UNSET` via `__post_init__` and the `category` field is no longer silently dropped by `omit_defaults` (PR #870).
6+
7+
### QOL Improvements
8+
9+
- **Trivy action updated to `0.35.0`**: Keeps the security scanning workflow on a stable release (PR #871).
10+
111
## 9.3.0 (March 16, 2026)
212

313
### New Features

pyatlan/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
9.3.0
1+
9.3.1

pyatlan_v9/model/assets/_overlays/asset.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,47 @@ def validate_required(self) -> None:
128128

129129
def flush_custom_metadata(self, client=None) -> None:
130130
"""
131-
Flush (clear) all custom metadata on this asset.
131+
Flush custom metadata to business_attributes.
132+
133+
In v9, set_custom_metadata already writes to business_attributes directly,
134+
so this is a no-op for compatibility with the legacy API.
135+
136+
Args:
137+
client: AtlanClient instance (for compatibility with legacy API)
138+
"""
139+
pass
140+
141+
def get_custom_metadata(self, client, name: str):
142+
"""Get custom metadata by name.
143+
144+
Args:
145+
client: AtlanClient instance
146+
name: human-readable name of the custom metadata set
147+
148+
Returns:
149+
CustomMetadataDict for the requested set
150+
"""
151+
from pyatlan_v9.model.custom_metadata import CustomMetadataProxy
152+
153+
proxy = CustomMetadataProxy(
154+
business_attributes=self.business_attributes, client=client
155+
)
156+
return proxy.get_custom_metadata(name=name)
157+
158+
def set_custom_metadata(self, custom_metadata, client=None) -> None:
159+
"""Set custom metadata on this asset.
132160
133161
Args:
162+
custom_metadata: CustomMetadataDict to set
134163
client: AtlanClient instance (for compatibility with legacy API)
135164
"""
136-
self.business_attributes = {}
165+
from pyatlan_v9.model.custom_metadata import CustomMetadataProxy
166+
167+
proxy = CustomMetadataProxy(
168+
business_attributes=self.business_attributes, client=client or custom_metadata._client
169+
)
170+
proxy.set_custom_metadata(custom_metadata=custom_metadata)
171+
self.business_attributes = proxy.business_attributes
137172

138173
async def get_custom_metadata_async(self, client, name: str):
139174
"""Async: get custom metadata by name."""

pyatlan_v9/model/assets/asset.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,12 +1178,47 @@ def validate_required(self) -> None:
11781178

11791179
def flush_custom_metadata(self, client=None) -> None:
11801180
"""
1181-
Flush (clear) all custom metadata on this asset.
1181+
Flush custom metadata to business_attributes.
1182+
1183+
In v9, set_custom_metadata already writes to business_attributes directly,
1184+
so this is a no-op for compatibility with the legacy API.
1185+
1186+
Args:
1187+
client: AtlanClient instance (for compatibility with legacy API)
1188+
"""
1189+
pass
1190+
1191+
def get_custom_metadata(self, client, name: str):
1192+
"""Get custom metadata by name.
1193+
1194+
Args:
1195+
client: AtlanClient instance
1196+
name: human-readable name of the custom metadata set
1197+
1198+
Returns:
1199+
CustomMetadataDict for the requested set
1200+
"""
1201+
from pyatlan_v9.model.custom_metadata import CustomMetadataProxy
1202+
1203+
proxy = CustomMetadataProxy(
1204+
business_attributes=self.business_attributes, client=client
1205+
)
1206+
return proxy.get_custom_metadata(name=name)
1207+
1208+
def set_custom_metadata(self, custom_metadata, client=None) -> None:
1209+
"""Set custom metadata on this asset.
11821210
11831211
Args:
1212+
custom_metadata: CustomMetadataDict to set
11841213
client: AtlanClient instance (for compatibility with legacy API)
11851214
"""
1186-
self.business_attributes = {}
1215+
from pyatlan_v9.model.custom_metadata import CustomMetadataProxy
1216+
1217+
proxy = CustomMetadataProxy(
1218+
business_attributes=self.business_attributes, client=client or custom_metadata._client
1219+
)
1220+
proxy.set_custom_metadata(custom_metadata=custom_metadata)
1221+
self.business_attributes = proxy.business_attributes
11871222

11881223
async def get_custom_metadata_async(self, client, name: str):
11891224
"""Async: get custom metadata by name."""

pyatlan_v9/model/custom_metadata.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,10 @@ def __init__(
119119
self._metadata: Union[dict[str, CustomMetadataDict], None] = None
120120
self._business_attributes = business_attributes
121121
self._modified = False
122-
if self._business_attributes is None:
122+
if self._business_attributes is None or not isinstance(
123+
self._business_attributes, dict
124+
):
125+
self._business_attributes = None
123126
return
124127
self._metadata = {}
125128
for cm_id, cm_attributes in self._business_attributes.items():

tests_v9/integration/custom_metadata_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,7 @@ def test_search_by_any_accountable(
849849
assert isinstance(t, AtlasGlossaryTerm)
850850
assert t.guid == term.guid
851851
assert t.qualified_name == term.qualified_name
852-
anchor = t.attributes.anchor
852+
anchor = t.anchor
853853
assert anchor
854854
assert anchor.name == glossary.name
855855
_validate_raci_attributes_replacement(
@@ -890,7 +890,7 @@ def test_search_by_specific_accountable(
890890
assert isinstance(t, AtlasGlossaryTerm)
891891
assert t.guid == term.guid
892892
assert t.qualified_name == term.qualified_name
893-
anchor = t.attributes.anchor
893+
anchor = t.anchor
894894
assert anchor
895895
assert anchor.name == glossary.name
896896

0 commit comments

Comments
 (0)