Skip to content

Commit 99d5bf9

Browse files
authored
Merge pull request #26 from dataplat/develop
v0.3.1
2 parents 73badf2 + b8f630c commit 99d5bf9

32 files changed

Lines changed: 1616 additions & 251 deletions

.github/actions/publish/action.yaml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ runs:
3434
shell: bash
3535

3636
- name: Setup python
37-
uses: actions/setup-python@v4
38-
with:
39-
python-version: ${{ inputs.PYTHON_VERSION }}
37+
run: uv python install ${{ inputs.PYTHON_VERSION }}
38+
shell: bash
4039

4140
- name: Build package
4241
run: uv build

.github/actions/test-docs/action.yaml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,6 @@ inputs:
99
description: Python version
1010
required: false
1111
default: "3.13"
12-
POSTGRES_VERSION:
13-
description: Postgres major version to use
14-
required: false
15-
default: 12
16-
CONTAINER_NETWORK:
17-
description: Docker container network to use
18-
required: false
19-
default: pgmob-network
2012

2113
runs:
2214
using: "composite"
@@ -33,9 +25,8 @@ runs:
3325
shell: bash
3426

3527
- name: Setup python
36-
uses: actions/setup-python@v4
37-
with:
38-
python-version: ${{ inputs.PYTHON_VERSION }}
28+
run: uv python install ${{ inputs.PYTHON_VERSION }}
29+
shell: bash
3930

4031
- name: Install dependencies
4132
run: uv sync
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
name: Auto Release from Changelog
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'CHANGELOG.md'
9+
10+
jobs:
11+
create-release:
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: write
15+
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
21+
22+
- name: Parse Changelog
23+
id: changelog
24+
run: |
25+
# Extract the latest version and its content from CHANGELOG.md
26+
python3 << 'EOF'
27+
import re
28+
import sys
29+
30+
with open('CHANGELOG.md', 'r') as f:
31+
content = f.read()
32+
33+
# Match version headers like ## [0.3.1] - 2026-02-10
34+
version_pattern = r'^## \[([^\]]+)\] - (\d{4}-\d{2}-\d{2})'
35+
matches = list(re.finditer(version_pattern, content, re.MULTILINE))
36+
37+
if not matches:
38+
print("No version found in CHANGELOG.md", file=sys.stderr)
39+
sys.exit(1)
40+
41+
# Get the first (latest) version
42+
first_match = matches[0]
43+
version = first_match.group(1)
44+
date = first_match.group(2)
45+
46+
# Extract content between first and second version headers
47+
start_pos = first_match.end()
48+
if len(matches) > 1:
49+
end_pos = matches[1].start()
50+
body = content[start_pos:end_pos].strip()
51+
else:
52+
# If only one version, get everything after it until the end or separator
53+
remaining = content[start_pos:]
54+
separator_match = re.search(r'^---$', remaining, re.MULTILINE)
55+
if separator_match:
56+
body = remaining[:separator_match.start()].strip()
57+
else:
58+
body = remaining.strip()
59+
60+
# Clean up the body - remove leading/trailing whitespace
61+
body = body.strip()
62+
63+
# Write outputs
64+
with open('version.txt', 'w') as f:
65+
f.write(version)
66+
with open('body.txt', 'w') as f:
67+
f.write(body)
68+
69+
print(f"Version: {version}")
70+
print(f"Date: {date}")
71+
print(f"Body length: {len(body)} characters")
72+
EOF
73+
74+
# Set outputs
75+
VERSION=$(cat version.txt)
76+
echo "version=$VERSION" >> $GITHUB_OUTPUT
77+
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
78+
79+
# For multiline output, use delimiter
80+
{
81+
echo 'body<<EOF_CHANGELOG'
82+
cat body.txt
83+
echo EOF_CHANGELOG
84+
} >> $GITHUB_OUTPUT
85+
86+
- name: Check if release exists
87+
id: check_release
88+
env:
89+
GH_TOKEN: ${{ github.token }}
90+
run: |
91+
TAG="${{ steps.changelog.outputs.tag }}"
92+
93+
# Check if release exists
94+
if gh release view "$TAG" &>/dev/null; then
95+
echo "Release $TAG already exists"
96+
echo "exists=true" >> $GITHUB_OUTPUT
97+
else
98+
echo "Release $TAG does not exist"
99+
echo "exists=false" >> $GITHUB_OUTPUT
100+
fi
101+
102+
- name: Create Release
103+
if: steps.check_release.outputs.exists == 'false'
104+
env:
105+
GH_TOKEN: ${{ github.token }}
106+
run: |
107+
TAG="${{ steps.changelog.outputs.tag }}"
108+
VERSION="${{ steps.changelog.outputs.version }}"
109+
110+
# Create release with changelog body
111+
gh release create "$TAG" \
112+
--title "Release $VERSION" \
113+
--notes "${{ steps.changelog.outputs.body }}" \
114+
--verify-tag
115+
116+
- name: Skip Release
117+
if: steps.check_release.outputs.exists == 'true'
118+
run: |
119+
echo "Release ${{ steps.changelog.outputs.tag }} already exists. Skipping creation."

.github/workflows/docs.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
paths:
66
- 'docs/**'
77
- '.github/**'
8+
- 'src/pgmob/**'
89
pull_request:
910
branches:
1011
- main

.github/workflows/release.yaml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,6 @@ jobs:
1414
uses: actions/checkout@v3
1515
- name: Run tests
1616
uses: ./.github/actions/test
17-
# - name: Bump version
18-
# uses: ./.github/actions/bump-version
19-
# with:
20-
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21-
# GIT_USERNAME: ${{ secrets.GIT_USERNAME }}
22-
# GIT_EMAIL: ${{ secrets.GIT_EMAIL }}
2317
- name: Publish to PyPI Test
2418
uses: ./.github/actions/publish
2519
with:

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ dist/
99
.DS_Store
1010
.mypy_cache/
1111
.pytest_cache/
12+
.hypothesis/
1213

1314
# coverage
1415
artifacts/
@@ -19,3 +20,6 @@ coverage.xml
1920
# docs build
2021
docs/_build
2122
.vscode/launch.json
23+
24+
# IDEs
25+
.kiro/specs/

.windsurf/rules/api-standards.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,150 @@ def owner(self, value: str) -> None:
224224
generic._set_ephemeral_attr(self, "owner", value)
225225
```
226226

227+
### Mixin Properties
228+
229+
Mixin properties follow the same API standards as regular properties. They provide consistent getter/setter behavior with change tracking.
230+
231+
#### Mixin Property Docstrings
232+
233+
Mixin properties MUST include docstrings that describe the property:
234+
235+
```python
236+
class NamedObjectMixin:
237+
"""Mixin providing name property with change tracking."""
238+
239+
@property
240+
def name(self) -> str:
241+
"""The object's name.
242+
243+
Returns:
244+
The name of the database object.
245+
"""
246+
return self._name
247+
248+
@name.setter
249+
def name(self, value: str) -> None:
250+
"""Set the object's name.
251+
252+
Changes are queued and applied when alter() is called.
253+
254+
Args:
255+
value: New name for the object.
256+
257+
Example:
258+
>>> table.name = "new_table_name"
259+
>>> table.alter() # Apply the name change
260+
"""
261+
from . import generic
262+
generic._set_ephemeral_attr(self, "name", value)
263+
```
264+
265+
#### Mixin Initialization in Object __init__
266+
267+
Objects using mixins MUST call mixin initialization methods in their `__init__`:
268+
269+
```python
270+
class Table(
271+
NamedObjectMixin,
272+
OwnedObjectMixin,
273+
SchemaObjectMixin,
274+
_DynamicObject,
275+
_CollectionChild
276+
):
277+
"""Postgres Table object.
278+
279+
Args:
280+
name: Table name
281+
schema: Schema name (default: 'public')
282+
owner: Table owner
283+
cluster: Postgres cluster object
284+
parent: Parent collection
285+
oid: Table OID
286+
287+
Attributes:
288+
name: Table name (from NamedObjectMixin)
289+
owner: Table owner (from OwnedObjectMixin)
290+
schema: Schema name (from SchemaObjectMixin)
291+
tablespace: Tablespace (from TablespaceObjectMixin)
292+
row_security: Whether row security is enabled
293+
oid: Table OID
294+
"""
295+
296+
def __init__(
297+
self,
298+
name: str,
299+
schema: str = "public",
300+
owner: Optional[str] = None,
301+
cluster: "Cluster" = None,
302+
parent: "TableCollection" = None,
303+
oid: Optional[int] = None
304+
):
305+
# Initialize base classes
306+
super().__init__(kind="TABLE", cluster=cluster, oid=oid, name=name, schema=schema)
307+
_CollectionChild.__init__(self, parent=parent)
308+
309+
# Initialize mixins - REQUIRED for mixin properties to work
310+
self._init_name(name)
311+
self._init_owner(owner)
312+
self._init_schema(schema)
313+
self._init_tablespace(None)
314+
315+
# Table-specific attributes
316+
self._row_security: bool = False
317+
```
318+
319+
#### Documenting Mixin Properties in Class Docstrings
320+
321+
When a class uses mixins, document which properties come from mixins in the class docstring:
322+
323+
```python
324+
class Sequence(
325+
NamedObjectMixin,
326+
OwnedObjectMixin,
327+
SchemaObjectMixin,
328+
_DynamicObject,
329+
_CollectionChild
330+
):
331+
"""Postgres sequence object.
332+
333+
Attributes:
334+
name: Sequence name (from NamedObjectMixin)
335+
owner: Sequence owner (from OwnedObjectMixin)
336+
schema: Schema name (from SchemaObjectMixin)
337+
data_type: Data type of the sequence
338+
start_value: Starting value
339+
increment_by: Increment value
340+
min_value: Minimum value
341+
max_value: Maximum value
342+
"""
343+
```
344+
345+
#### Mixin Property Behavior
346+
347+
Mixin properties behave identically to regular properties:
348+
349+
- **Getters**: Return the current value
350+
- **Setters**: Queue changes via `_set_ephemeral_attr()` for later application
351+
- **Change tracking**: Changes are stored in `self._changes` dictionary
352+
- **Application**: Changes are applied when `alter()` is called
353+
354+
```python
355+
# Using mixin properties
356+
table = cluster.tables["users"]
357+
358+
# Get property (from NamedObjectMixin)
359+
print(table.name) # "users"
360+
361+
# Set property (queues change)
362+
table.name = "app_users"
363+
364+
# Property is updated locally
365+
print(table.name) # "app_users"
366+
367+
# Apply change to database
368+
table.alter() # Executes: ALTER TABLE users RENAME TO app_users
369+
```
370+
227371
### Lazy Properties
228372
```python
229373
from ._decorators import get_lazy_property

0 commit comments

Comments
 (0)