Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Dependabot — keeps Python (uv) dependencies and GitHub Actions current.
# Security-patch PRs arrive out-of-band the moment a CVE is published.

version: 2
updates:
- package-ecosystem: uv
directory: /
schedule:
interval: weekly
day: monday
time: '06:00'
timezone: Europe/Vilnius
open-pull-requests-limit: 10
labels:
- dependencies
commit-message:
prefix: 'chore(deps)'
groups:
python-minor-and-patch:
update-types:
- minor
- patch

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
day: monday
time: '06:00'
timezone: Europe/Vilnius
open-pull-requests-limit: 5
labels:
- dependencies
- ci
commit-message:
prefix: 'ci'
groups:
actions-minor-and-patch:
update-types:
- minor
- patch
48 changes: 41 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,56 @@ on:
push:
branches: [main, next, beta]

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-22.04
name: Python ${{ matrix.python }}
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
# Oldest supported (requires-python >=3.10), the main dev version, newest.
python: ['3.10', '3.12', '3.13']
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10.1.0
with:
version: "latest"
- uses: actions/setup-python@v5
version: 'latest'
- uses: actions/setup-python@v7
with:
python-version: "3.12"
python-version: ${{ matrix.python }}
- name: Install dependencies
run: uv sync --all-groups
run: uv sync --all-groups --python ${{ matrix.python }}
- name: Lint
run: uv run ruff check .
- name: Typecheck
run: uv run mypy src
- name: Test
run: uv run pytest

package:
name: Build package
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10.1.0
with:
version: 'latest'
- name: uv build
run: uv build
- name: Check contents
run: |
ls -la dist
python3 - <<'EOF'
import sys, zipfile, glob
whl = glob.glob("dist/*.whl")[0]
names = zipfile.ZipFile(whl).namelist()
print(len(names), "files in", whl)
if not any(n.startswith("norbix_python/") for n in names):
sys.exit("::error::norbix_python package missing from the wheel")
leaked = [n for n in names if n.startswith(("tests/", "references/", "docs/"))]
if leaked:
sys.exit(f"::error::files that must not ship: {leaked}")
EOF
103 changes: 81 additions & 22 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,36 @@
name: Release

# Publishes `norbix` to PyPI via python-semantic-release on every push to `main`
# (and prerelease branches `next` / `beta`). The next version comes from
# conventional commits since the last `v*` tag.
#
# `main` is protected (changes only through pull requests), so the release does
# NOT commit back: `semantic-release version --no-commit` stamps the version
# into pyproject.toml / __init__.py only in the job's working tree, builds with
# it, tags the current commit, pushes just the tag and creates the GitHub
# Release. `version` in pyproject.toml on main stays stale; the tag is the
# source of truth. CHANGELOG.md is no longer written (release notes live on
# GitHub Releases).
#
# Required secrets:
# - PYPI_API_TOKEN — PyPI API token for the `norbix` project.
# - GITHUB_TOKEN — provided automatically; tag + GitHub Release.

on:
push:
branches: [main, next, beta]
workflow_dispatch:
inputs:
republish:
description: >-
Recovery mode — skip semantic-release and build + publish the latest
git tag to PyPI (use when a tag exists but the PyPI upload failed).
type: boolean
default: false

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: write
Expand All @@ -13,49 +40,81 @@ permissions:

jobs:
release:
runs-on: ubuntu-22.04
name: semantic-release
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0

- uses: astral-sh/setup-uv@v5
- name: Sync branch tip
# A run checks out its triggering SHA. When several merges land close
# together that SHA is already behind origin; release from the remote
# tip instead so a quick follow-up merge never swallows a release.
if: github.event_name != 'workflow_dispatch' || inputs.republish != true
run: |
git fetch origin "${GITHUB_REF_NAME}"
git checkout "${GITHUB_REF_NAME}"
git reset --hard "origin/${GITHUB_REF_NAME}"

- uses: astral-sh/setup-uv@v10.1.0
with:
version: "latest"
version: 'latest'

- uses: actions/setup-python@v5
- uses: actions/setup-python@v7
with:
python-version: "3.12"
python-version: '3.12'

- name: Install dependencies
run: uv sync --all-groups

- name: Test before release
run: |
uv run pytest
- name: Lint
run: uv run ruff check .

- name: Bootstrap initial semantic-release tag
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if ! git tag -l "v*" | grep -q .; then
echo "No semantic-release tags found. Bootstrapping v0.0.0"
git tag v0.0.0
git push origin v0.0.0
else
echo "Existing semantic-release tags found. Skipping bootstrap."
fi
- name: Typecheck
run: uv run mypy src

- name: Test
run: uv run pytest

- name: Semantic release version
if: github.event_name != 'workflow_dispatch' || inputs.republish != true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: uv run semantic-release version
# --no-commit: stamp + build + tag without a commit (main is protected).
# --push pushes the tag only and enables the GitHub Release.
run: uv run semantic-release version --no-commit --no-changelog --push

- name: Republish build (recovery)
if: github.event_name == 'workflow_dispatch' && inputs.republish == true
run: |
TAG=$(git describe --tags --abbrev=0 --match 'v[0-9]*')
VERSION="${TAG#v}"
echo "Recovery build: ${VERSION} (from tag ${TAG})"
git checkout --detach "${TAG}"
uv sync --all-groups
uv version "${VERSION}"
sed -i -E "s/^__version__ = \".*\"/__version__ = \"${VERSION}\"/" src/norbix_python/__init__.py
rm -rf dist
uv build

- name: Publish to PyPI
env:
PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
run: |
if ls dist/*.whl >/dev/null 2>&1; then
uv publish --token "$PYPI_TOKEN" dist/*
# --check-url skips files PyPI already has, so a re-run is safe.
uv publish --token "$PYPI_TOKEN" --check-url https://pypi.org/simple/ dist/*
else
echo "No new release artifacts produced; skipping PyPI publish."
fi

- name: Summary
if: always()
run: |
echo "### Release · ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
TAG=$(git describe --tags --abbrev=0 --match 'v[0-9]*' 2>/dev/null || true)
if [ -n "$TAG" ]; then
echo "Latest tag: \`${TAG}\` — notes: ${{ github.server_url }}/${{ github.repository }}/releases/tag/${TAG}" >> $GITHUB_STEP_SUMMARY
fi
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# CHANGELOG

> Release notes after 1.1.1 are published on [GitHub Releases](https://github.com/norbix-code/sdk-python/releases); this file is no longer updated.

<!-- version list -->

## v1.1.1 (2026-04-29)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# norbix-python

[![CI](https://github.com/norbix-dev/norbix-python/actions/workflows/ci.yml/badge.svg)](https://github.com/norbix-dev/norbix-python/actions/workflows/ci.yml)
[![CI](https://github.com/norbix-code/sdk-python/actions/workflows/ci.yml/badge.svg)](https://github.com/norbix-code/sdk-python/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/norbix.svg)](https://pypi.org/project/norbix/)
[![Python](https://img.shields.io/badge/python-%3E=3.10-blue)](https://python.org)
[![License](https://img.shields.io/pypi/l/norbix.svg)](./LICENSE)

Official Python SDK for [Norbix](https://norbix.dev).
Official Python SDK for [Norbix](https://norbix.ai).
Use split clients with flat module access:

- `NorbixApi` for API scope (`client.database`, `client.membership`, ...)
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ keywords = ["norbix", "sdk", "python", "api", "hub", "baas"]
dependencies = ["httpx>=0.27.0", "pydantic>=2.0"]

[project.urls]
Homepage = "https://norbix.dev"
Repository = "https://github.com/norbix-dev/norbix-python"
Issues = "https://github.com/norbix-dev/norbix-python/issues"
Homepage = "https://norbix.ai"
Repository = "https://github.com/norbix-code/sdk-python"
Issues = "https://github.com/norbix-code/sdk-python/issues"

[dependency-groups]
dev = [
Expand Down
6 changes: 4 additions & 2 deletions src/norbix_python/webhooks/receiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import inspect
import json
import os
from collections.abc import Awaitable, Callable, Mapping, Sequence
from collections.abc import Callable, Mapping, Sequence
from typing import Any

from .errors import NorbixWebhookParseError, NorbixWebhookSignatureError
Expand Down Expand Up @@ -169,7 +169,9 @@ def _build_event(
return payload, event, ctx

@staticmethod
def _result(envelope: WebhookEnvelope, verified: bool | None, handled: bool) -> WebhookHandleResult:
def _result(
envelope: WebhookEnvelope, verified: bool | None, handled: bool
) -> WebhookHandleResult:
return WebhookHandleResult(
event=envelope.event,
deliveryId=envelope.id,
Expand Down
Loading