Skip to content

Commit d5e69db

Browse files
committed
Merge branch 'release/v1.0.1'
2 parents 8527662 + 2dadfed commit d5e69db

42 files changed

Lines changed: 9423 additions & 25 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/auto-response.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
runs-on: ubuntu-latest
1010
steps:
1111
- name: Post auto-response
12-
uses: actions/github-script@v7
12+
uses: actions/github-script@v8
1313
with:
1414
script: |
1515
github.rest.issues.createComment({

.github/workflows/create-release.yml

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
name: Create Github Release
22

33
on:
4-
push:
5-
tags:
6-
- "v[0-9]+.[0-9]+.[0-9]+"
7-
- "v[0-9]+.[0-9]+.[0-9]+[a-zA-Z]+[0-9]*"
4+
workflow_run:
5+
workflows: ["Run Tests"]
6+
types:
7+
- completed
8+
branches:
9+
- '**'
810
workflow_dispatch:
911
inputs:
1012
tag:
@@ -15,13 +17,19 @@ on:
1517
jobs:
1618
create-release:
1719
runs-on: ubuntu-latest
20+
# Only run if tests passed and this is a tag push, or manual dispatch
21+
if: |
22+
github.event_name == 'workflow_dispatch' ||
23+
(github.event.workflow_run.conclusion == 'success' && startsWith(github.event.workflow_run.head_branch, 'refs/tags/v'))
1824
1925
permissions:
2026
contents: write
2127

2228
steps:
2329
- name: Checkout code
24-
uses: actions/checkout@v4
30+
uses: actions/checkout@v6
31+
with:
32+
ref: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.tag) || github.event.workflow_run.head_branch }}
2533

2634
- name: Extract version information
2735
id: version_info
@@ -30,7 +38,7 @@ jobs:
3038
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
3139
VERSION="${{ github.event.inputs.tag }}"
3240
else
33-
VERSION="${{ github.ref_name }}"
41+
VERSION="${{ github.event.workflow_run.head_branch }}"
3442
fi
3543
3644
echo "version=$VERSION" >> $GITHUB_OUTPUT
@@ -85,13 +93,13 @@ jobs:
8593
id: create_release
8694
run: |
8795
if [ "${{ steps.version_info.outputs.is_prerelease }}" = "true" ]; then
88-
echo "Create release for version ${{ github.ref_name }} (Pre-release)"
96+
echo "Create release for version ${{ steps.version_info.outputs.version }} (Pre-release)"
8997
gh release create "${{ steps.version_info.outputs.version }}" \
9098
--title "${{ steps.version_info.outputs.release_name }}" \
9199
--notes-file release_notes.md \
92100
--prerelease
93101
else
94-
echo "Create release for version ${{ github.ref_name }}"
102+
echo "Create release for version ${{ steps.version_info.outputs.version }}"
95103
gh release create "${{ steps.version_info.outputs.version }}" \
96104
--title "${{ steps.version_info.outputs.release_name }}" \
97105
--notes-file release_notes.md

.github/workflows/publish-pypi.yml

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,58 @@
11
name: Publish to PyPi
22

33
on:
4-
push:
5-
tags:
6-
- "v[0-9]+.[0-9]+.[0-9]+"
7-
- "v[0-9]+.[0-9]+.[0-9]+[a-zA-Z]+[0-9]"
4+
workflow_run:
5+
workflows: ["Run Tests"]
6+
types:
7+
- completed
8+
branches:
9+
- '**'
810
workflow_dispatch:
11+
inputs:
12+
tag:
13+
description: "Tag to checkout and publish (e.g., v2.0.0)"
14+
required: true
15+
type: string
16+
default: ""
17+
18+
permissions:
19+
contents: read
920

1021
jobs:
1122
publish-and-release:
1223
runs-on: ubuntu-latest
1324
environment: publish
25+
# Only run if tests passed and this is a tag push, or manual dispatch
26+
if: |
27+
github.event_name == 'workflow_dispatch' ||
28+
(github.event.workflow_run.conclusion == 'success' && startsWith(github.event.workflow_run.head_branch, 'refs/tags/v'))
1429
1530
steps:
1631
- name: Checkout code
17-
uses: actions/checkout@v4
32+
uses: actions/checkout@v6
33+
with:
34+
fetch-depth: 0
35+
ref: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' && github.event.inputs.tag) || github.event.workflow_run.head_branch }}
36+
37+
- name: Verify tag exists (only for dispatch)
38+
if: github.event_name == 'workflow_dispatch'
39+
run: |
40+
git fetch --tags
41+
if ! git rev-parse --verify "refs/tags/${{ github.event.inputs.tag }}" > /dev/null; then
42+
echo "ERROR: Tag '${{ github.event.inputs.tag }}' does not exist"
43+
echo "Available tags:"
44+
git tag --sort=-version:refname | head -10
45+
exit 1
46+
fi
47+
echo "Tag '${{ github.event.inputs.tag }}' verified successfully"
1848
1949
- name: Set up Python
20-
uses: actions/setup-python@v5
50+
uses: actions/setup-python@v6
2151
with:
2252
python-version-file: ".python-version"
2353

2454
- name: Install uv
25-
uses: astral-sh/setup-uv@v6
55+
uses: astral-sh/setup-uv@v7
2656

2757
- name: Publish to PyPI
2858
run: |

.github/workflows/test.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Run Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- '**'
7+
tags:
8+
- 'v[0-9]+.[0-9]+.[0-9]+'
9+
- 'v[0-9]+.[0-9]+.[0-9]+[a-zA-Z]+[0-9]*'
10+
pull_request:
11+
branches:
12+
- '**'
13+
14+
jobs:
15+
test:
16+
runs-on: ubuntu-latest
17+
18+
strategy:
19+
matrix:
20+
python-version: ['3.9', '3.10', '3.11', '3.12']
21+
22+
steps:
23+
- name: Checkout code
24+
uses: actions/checkout@v6
25+
26+
- name: Set up Python ${{ matrix.python-version }}
27+
uses: actions/setup-python@v6
28+
with:
29+
python-version: ${{ matrix.python-version }}
30+
31+
- name: Install uv
32+
uses: astral-sh/setup-uv@v7
33+
34+
- name: Install dependencies
35+
run: uv sync --group dev
36+
37+
- name: Run linting and type checks
38+
run: uv run poe lint
39+
40+
- name: Run tests
41+
run: uv run pytest -q
42+
43+
- name: Upload coverage reports
44+
if: matrix.python-version == '3.9'
45+
uses: codecov/codecov-action@v5
46+
continue-on-error: true
47+
with:
48+
token: ${{ secrets.CODECOV_TOKEN }}

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ dist
88

99
.env
1010

11-
.DS_Store
11+
.DS_Store
12+
13+
cov.xml
14+
.coverage

CONTRIBUTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ uv run poe format
3434
uv run poe lint
3535
```
3636

37+
## Testing
38+
39+
We use [pytest](https://docs.pytest.org) as testing framework.
40+
41+
```bash
42+
uv run poe test
43+
```
44+
3745
## Publishing and release
3846

3947
Update version with `uv version --bump`:

pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "payos"
3-
version = "1.0.0"
3+
version = "1.0.1"
44
description = "payOS Python SDK"
55
readme = "README.md"
66
requires-python = ">=3.9"
@@ -109,6 +109,7 @@ force-wrap-aliases = true
109109
"typecheck" = [{ cmd = "mypy ./src" }]
110110
format = ["fix:ruff", "format:ruff"]
111111
lint = ["check:ruff", "typecheck", "check:importable"]
112+
test = [{ cmd = "pytest" }]
112113

113114
# Combined tasks
114115
check = ["format", "lint"]
@@ -125,7 +126,7 @@ testpaths = ["tests"]
125126
python_files = ["test_*.py", "*_test.py"]
126127
python_classes = ["Test*"]
127128
python_functions = ["test_*"]
128-
addopts = "-v --tb=short"
129+
addopts = "-v --tb=short --cov=payos --cov-report=xml:cov.xml"
129130
asyncio_mode = "auto"
130131

131132
[dependency-groups]
@@ -138,5 +139,6 @@ dev = [
138139
"mypy>=1.17.1",
139140
"ruff>=0.12.11",
140141
"poethepoet>=0.37.0",
142+
"pytest-cov>=7.0.0",
141143
]
142144
example = ["flask>=3.1.2", "python-dotenv>=1.1.1"]

src/payos/_crypto/provider.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def create_signature_from_object(
110110
self, data: Union[dict[str, Any], Any], key: str
111111
) -> Optional[str]:
112112
"""Create HMAC signature from object data."""
113-
if not data or not key:
113+
if data is None or not key:
114114
return None
115115

116116
# Convert Pydantic models to camelCase dict
@@ -128,7 +128,7 @@ def create_signature_of_payment_request(
128128
self, data: Union[dict[str, Any], Any], key: str
129129
) -> Optional[str]:
130130
"""Create signature for payment request using specific fields."""
131-
if not data or not key:
131+
if data is None or not key:
132132
return None
133133

134134
data = _convert_to_camel_case_dict(data)

src/payos/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# Do not update manually, this will be generate when building
2-
__version__ = "1.0.0"
2+
__version__ = "1.0.1"

src/payos/resources/webhooks/webhooks.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,31 @@ async def confirm(self, webhook_url: str, **kwargs: Any) -> ConfirmWebhookRespon
116116
except Exception as error:
117117
raise error
118118

119-
async def verify(self, webhook: Webhook) -> WebhookData:
119+
async def verify(self, payload: Union[str, bytes, dict, Webhook]) -> WebhookData:
120120
"""
121121
Verify the webhook data sent from payOS.
122122
"""
123+
if isinstance(payload, Webhook):
124+
webhook = payload
125+
else:
126+
if isinstance(payload, (bytes, bytearray)):
127+
payload_str = payload.decode("utf-8")
128+
payload_obj = safe_json_parse(payload_str)
129+
if not payload_obj:
130+
raise WebhookError("Invalid JSON")
131+
elif isinstance(payload, str):
132+
payload_obj = safe_json_parse(payload)
133+
if not payload_obj:
134+
raise WebhookError("Invalid JSON")
135+
elif isinstance(payload, dict):
136+
payload_obj = payload
137+
else:
138+
raise WebhookError(f"Unsupported payload type: {type(payload)}")
139+
try:
140+
webhook = Webhook(**payload_obj)
141+
except ValidationError as e:
142+
raise WebhookError(f"Webhook schema validation failed: {e}") from e
143+
123144
data = webhook.data
124145
signature = webhook.signature
125146

0 commit comments

Comments
 (0)