diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..08e7afe --- /dev/null +++ b/.github/dependabot.yml @@ -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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4412930..7d38be1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03d5a28..3560c4c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index d22a86f..6c55663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. + ## v1.1.1 (2026-04-29) diff --git a/README.md b/README.md index 9894afc..8d4c963 100644 --- a/README.md +++ b/README.md @@ -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`, ...) diff --git a/pyproject.toml b/pyproject.toml index 41412ea..91aa854 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 = [ diff --git a/src/norbix_python/webhooks/receiver.py b/src/norbix_python/webhooks/receiver.py index 51a1601..e289943 100644 --- a/src/norbix_python/webhooks/receiver.py +++ b/src/norbix_python/webhooks/receiver.py @@ -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 @@ -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,