diff --git a/.github/workflows/build-python.yaml b/.github/workflows/build-python.yaml index 0b023d6..9510b2f 100644 --- a/.github/workflows/build-python.yaml +++ b/.github/workflows/build-python.yaml @@ -39,15 +39,5 @@ jobs: with: name: python-wheel path: dist/*.whl + overwrite: true - integration-test: - strategy: - fail-fast: true - matrix: - python3_version: ["3.10", "3.11", "3.12", "3.13"] - # python3_version: ["3.13"] - needs: build - uses: ./.github/workflows/platform-integration-test.yaml - with: - wheel: ${{ needs.build.outputs.wheel }} - python_version: ${{ matrix.python3_version }} diff --git a/.github/workflows/publish-test.yaml b/.github/workflows/publish-test.yaml index c6aadb6..8398589 100644 --- a/.github/workflows/publish-test.yaml +++ b/.github/workflows/publish-test.yaml @@ -1,10 +1,12 @@ name: TestPyPIBuild on: - push: - branches: - - develop - - chore/rewrite-in-python + # push: + # branches: + # - develop + # - chore/update-docs-and-release-process + # repository_dispatch: + # types: [alpha-release-created, stable-release-created] workflow_dispatch: jobs: @@ -41,8 +43,8 @@ jobs: id-token: write needs: [build] runs-on: ubuntu-22.04 - # If branch is 'develop' - if: github.ref == 'refs/heads/develop' + # If branch is 'develop' or triggered by repository dispatch + if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/chore/update-docs-and-release-process' || github.event_name == 'repository_dispatch' steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 2ec1c72..5eaedc9 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -1,9 +1,10 @@ name: PyPIBuild on: - push: - branches: - - main + release: + types: [published] + repository_dispatch: + types: [alpha-release-created, stable-release-created] workflow_dispatch: jobs: @@ -106,8 +107,8 @@ jobs: id-token: write needs: [build_macos, build_linux_x86_64, build_linux_arm] runs-on: ubuntu-22.04 - # If branch is 'main' - if: github.ref == 'refs/heads/main' + # If branch is 'main' or triggered by repository dispatch + if: github.ref == 'refs/heads/main' || github.event_name == 'repository_dispatch' || github.event_name == 'release' steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/release-please.yaml b/.github/workflows/release-please.yaml new file mode 100644 index 0000000..3335741 --- /dev/null +++ b/.github/workflows/release-please.yaml @@ -0,0 +1,74 @@ +name: Release Please + +on: + push: + branches: + - main + - develop + - chore/update-docs-and-release-process + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + # Run full test suite before any release operations + test-suite: + uses: ./.github/workflows/test-suite.yaml + + release-please: + runs-on: ubuntu-latest + needs: test-suite + if: needs.test-suite.outputs.tests_passed == 'true' + outputs: + releases_created: ${{ steps.release.outputs.releases_created }} + paths_released: ${{ steps.release.outputs.paths_released }} + steps: + - uses: googleapis/release-please-action@v4 + id: release + with: + config-file: .release-please-config.json + manifest-file: .release-please-manifest.json + target-branch: develop # FIXME: Change to 'main' after initial setup + token: ${{ secrets.GITHUB_TOKEN }} + + # Trigger appropriate publish workflows based on release type + trigger-publish: + needs: release-please + if: ${{ needs.release-please.outputs.releases_created }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check if alpha release + id: check_alpha + run: | + # Get the version from the main package + VERSION=$(cat .release-please-manifest.json | jq -r '."."') + echo "version=$VERSION" >> $GITHUB_OUTPUT + if [[ "$VERSION" =~ [0-9]+\.[0-9]+\.[0-9]+a[0-9]+ ]]; then + echo "is_alpha=true" >> $GITHUB_OUTPUT + echo "Alpha version detected: $VERSION" + else + echo "is_alpha=false" >> $GITHUB_OUTPUT + echo "Stable version detected: $VERSION" + fi + + # For develop branch: trigger TestPyPI build (both alpha and stable releases go to TestPyPI from develop) + - name: Trigger TestPyPI Build (Develop) + if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/chore/update-docs-and-release-process' + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + event-type: ${{ steps.check_alpha.outputs.is_alpha == 'true' && 'alpha-release-created' || 'stable-release-created' }} + client-payload: '{"version": "${{ steps.check_alpha.outputs.version }}", "releases": "${{ needs.release-please.outputs.paths_released }}"}' + + # For main branch: trigger PyPI build (both alpha and stable releases go to PyPI from main) + - name: Trigger PyPI Build (Main) + if: github.ref == 'refs/heads/main' + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + event-type: ${{ steps.check_alpha.outputs.is_alpha == 'true' && 'alpha-release-created' || 'stable-release-created' }} + client-payload: '{"version": "${{ steps.check_alpha.outputs.version }}", "releases": "${{ needs.release-please.outputs.paths_released }}"}' diff --git a/.github/workflows/test-suite.yaml b/.github/workflows/test-suite.yaml new file mode 100644 index 0000000..a3c33c0 --- /dev/null +++ b/.github/workflows/test-suite.yaml @@ -0,0 +1,121 @@ +name: Test Suite + +on: + push: + branches: + - main + - develop + pull_request: + workflow_call: + outputs: + tests_passed: + description: "Whether all tests passed" + value: ${{ jobs.report.outputs.success }} + workflow_dispatch: + +jobs: + # Step 1: Fast lint and format checks (fail fast on code style) + lint-check: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: Set up uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + + - name: Run linting (fail fast) + run: | + uv sync --frozen + uv run ruff check + uv run ruff format --check + + # Step 2: Build (only after linting passes) + build: + runs-on: ubuntu-22.04 + needs: lint-check + outputs: + wheel: ${{ steps.find_wheel.outputs.wheel_path }} + steps: + - name: Checkout this repo + uses: actions/checkout@v4 + + - name: Set up uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + + - name: Build otdf-python wheel using uv + run: | + uv sync --frozen + uv build + shell: bash + + - name: Find built wheel + id: find_wheel + run: | + wheel_path=$(ls dist/*.whl | head -n1) + echo "wheel_path=$wheel_path" >> $GITHUB_OUTPUT + shell: bash + + - name: Upload wheel as artifact + uses: actions/upload-artifact@v4 + with: + name: python-wheel + path: dist/*.whl + + # Step 3: Unit tests (only after build succeeds) + unit-tests: + runs-on: ubuntu-22.04 + needs: build + steps: + - uses: actions/checkout@v4 + + - name: Set up uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + + - name: Run unit tests + run: | + uv sync --frozen + uv run pytest -m "not integration" --tb=short -v tests/ + + # Step 4: Integration tests (only after unit tests pass) + integration-tests: + strategy: + fail-fast: true + matrix: + python3_version: ["3.10", "3.11", "3.12", "3.13"] + needs: [build, unit-tests] + uses: ./.github/workflows/platform-integration-test.yaml + with: + wheel: ${{ needs.build.outputs.wheel }} + python_version: ${{ matrix.python3_version }} + + report: + runs-on: ubuntu-22.04 + needs: [lint-check, build, unit-tests, integration-tests] + if: always() + outputs: + success: ${{ steps.check.outputs.success }} + steps: + - name: Check all jobs succeeded + id: check + run: | + if [[ "${{ needs.lint-check.result }}" == "success" && "${{ needs.build.result }}" == "success" && "${{ needs.unit-tests.result }}" == "success" && "${{ needs.integration-tests.result }}" == "success" ]]; then + echo "success=true" >> $GITHUB_OUTPUT + echo "✅ All tests passed!" + else + echo "success=false" >> $GITHUB_OUTPUT + echo "❌ Some tests failed:" + echo " Lint Check: ${{ needs.lint-check.result }}" + echo " Build: ${{ needs.build.result }}" + echo " Unit Tests: ${{ needs.unit-tests.result }}" + echo " Integration Tests: ${{ needs.integration-tests.result }}" + exit 1 + fi \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 76053b0..de489ad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,8 @@ --- +default_install_hook_types: + - pre-commit + - commit-msg + - post-rewrite exclude: | (?x)^( otdf_python/.* @@ -17,26 +21,6 @@ repos: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - - # - repo: https://github.com/tekwizely/pre-commit-golang - # rev: master - # hooks: - # - id: go-lint - - - repo: https://github.com/dnephin/pre-commit-golang - rev: v0.5.1 - hooks: - - id: go-fmt - - - id: go-vet - # - id: golangci-lint - # timeout is needed for CI - # args: [-E, gosec, -E, goconst, -E, govet, --timeout, 300s] - # - id: go-imports - # - id: go-cyclo - # args: [-over=15] - # - id: validate-toml - - id: no-go-testing - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: @@ -45,9 +29,15 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.12.10 + rev: v0.12.12 hooks: # Run the linter. - id: ruff # Run the formatter. - id: ruff-format + - repo: https://github.com/compilerla/conventional-pre-commit + rev: v4.2.0 + hooks: + - id: conventional-pre-commit + stages: [commit-msg,post-rewrite] + args: [--verbose,--scopes=feat,fix,docs,style,test,chore,ci] diff --git a/.release-please-config.json b/.release-please-config.json new file mode 100644 index 0000000..e8f81e7 --- /dev/null +++ b/.release-please-config.json @@ -0,0 +1,31 @@ +{ + "release-type": "python", + "include-v-in-tag": true, + "packages": { + ".": { + "release-type": "python", + "package-name": "otdf-python", + "extra-files": [ + "src/otdf_python/cli.py", + "tests/test_cli.py" + ] + }, + "otdf-python-proto": { + "release-type": "python", + "package-name": "otdf-python-proto" + } + }, + "changelog-sections": [ + { "type": "feat", "section": "Features", "hidden": false }, + { "type": "fix", "section": "Bug Fixes", "hidden": false }, + { "type": "perf", "section": "Performance Improvements", "hidden": false }, + { "type": "revert", "section": "Reverts", "hidden": false }, + { "type": "docs", "section": "Documentation", "hidden": false }, + { "type": "style", "section": "Styles", "hidden": true }, + { "type": "chore", "section": "Miscellaneous Chores", "hidden": true }, + { "type": "refactor", "section": "Code Refactoring", "hidden": false }, + { "type": "test", "section": "Tests", "hidden": true }, + { "type": "build", "section": "Build System", "hidden": false }, + { "type": "ci", "section": "Continuous Integration", "hidden": true } + ] +} \ No newline at end of file diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..671493e --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,4 @@ +{ + ".": "0.3.0a9", + "otdf-python-proto": "0.3.0a9" +} diff --git a/PROTOBUF_SETUP.md b/PROTOBUF_SETUP.md index b779957..053d061 100644 --- a/PROTOBUF_SETUP.md +++ b/PROTOBUF_SETUP.md @@ -86,7 +86,7 @@ curl -o proto-gen/proto-files/kas.proto https://raw.githubusercontent.com/opentd ## Dependencies -The proto-gen sub-module includes these dependencies: +The otdf-python-proto sub-module includes these dependencies: - `grpcio>=1.74.0` - gRPC runtime - `grpcio-tools>=1.74.0` - Protocol buffer compiler - `protobuf>=6.31.1` - Protocol buffer runtime @@ -128,7 +128,7 @@ The generated Python files include: - **`kas_pb2.py`** - Protocol buffer message classes - **`kas_pb2_grpc.py`** - gRPC service client and server classes -These files are automatically synced to `src/otdf_python/proto/` in the main project. +These files are automatically synced to `otdf-python-proto/generated/` and used by the main project in `src/otdf_python/`. ## Fallback Strategy diff --git a/README.md b/README.md index 92100c6..7bd008c 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,21 @@ Install from PyPI: pip install otdf-python ``` + +## Protobuf & Connect RPC Generation + +This project uses a dedicated submodule, `otdf-python-proto/`, for generating Python protobuf files and Connect RPC clients from OpenTDF platform proto definitions. + +### Regenerating Protobuf & Connect RPC Files + +From the submodule: +```bash +cd otdf-python-proto +uv run python scripts/generate_connect_proto.py +``` + +See [`otdf-python-proto/README.md`](otdf-python-proto/README.md) and [`PROTOBUF_SETUP.md`](PROTOBUF_SETUP.md) for details. + ## Quick Start ### Basic Configuration @@ -185,21 +200,15 @@ decrypted_data = decrypted_stream.getvalue() ``` src/otdf_python/ -├── __init__.py # Package initialization -├── sdk.py # Main SDK interface +├── sdk.py # Main SDK interface ├── config.py # Configuration management ├── tdf.py # TDF format handling ├── nanotdf.py # NanoTDF format handling ├── crypto_utils.py # Cryptographic utilities ├── kas_client.py # Key Access Service client └── ... # Additional modules - tests/ -├── test_sdk.py # SDK tests -├── test_config.py # Configuration tests -├── test_tdf.py # TDF format tests -└── ... # Additional test files -``` +└── ... # Various tests ## Contributing @@ -211,6 +220,13 @@ tests/ 6. Push to the branch: `git push origin feature-name` 7. Submit a pull request +### Release Process + +For maintainers and contributors working on releases: +- See [RELEASES.md](RELEASES.md) for comprehensive release documentation +- Feature branch alpha releases available for testing changes before merge +- Automated releases via Release Please on the main branch + ## License This project is licensed under the MIT License - see the LICENSE file for details. diff --git a/RELEASES.md b/RELEASES.md new file mode 100644 index 0000000..6ea4a84 --- /dev/null +++ b/RELEASES.md @@ -0,0 +1,329 @@ +# Release Process for Maintainers + +This document provides comprehensive guidance for maintainers on the OpenTDF Python SDK release process, including both automated releases and feature branch testing. + +## Release Overview + +The OpenTDF Python SDK uses **Release Please** for automated version management and publishing. The system supports: + +- **Alpha releases** (e.g., `0.3.0a9`): Automated publishing to both test.pypi.org and pypi.org +- **Stable releases** (e.g., `0.3.0`): Automated publishing to pypi.org only +- **Feature branch testing**: Manual alpha releases from development branches + +## Current Version Status + +```bash +# Check current version +uv version --short + +# Preview next alpha version +uv version --bump alpha --dry-run + +# Preview next stable version +uv version --bump minor --dry-run # or patch/major +``` + +## Automated Release Process (Main Branch) + +### Prerequisites + +✅ **All tests must pass** before any release: +- Unit tests: `uv run pytest tests/` +- Integration tests: `uv run pytest tests/ -m integration` +- Linting: `uv run ruff check` and `uv run ruff format` +- Platform integration tests (via GitHub Actions) + +### Standard Release Flow + +1. **Commit with Conventional Commit Messages** to `main` branch: + ```bash + git commit -m "feat: add new encryption algorithm support" + git commit -m "fix: resolve TDF decryption issue with large files" + git commit -m "docs: update SDK configuration examples" + ``` + +2. **Push to Main**: + ```bash + git push origin main + ``` + +3. **Automated Process**: + - Release Please creates a PR with version bump and changelog + - Once PR is merged, GitHub Actions automatically: + - Runs full test suite (unit, integration, platform tests) + - Builds wheels for multiple platforms (macOS, Linux x86_64, Linux ARM) + - Publishes to PyPI (alpha versions go to both test.pypi.org and pypi.org) + - Creates GitHub release with artifacts + +### Release Type Determination + +The system automatically determines release type based on version format: + +- **Alpha**: `X.Y.ZaN` (e.g., `0.3.0a9`) → Published to test.pypi.org + pypi.org +- **Stable**: `X.Y.Z` (e.g., `0.3.0`) → Published to pypi.org only + +## Manual Release Management + +### Bootstrap Release Please (First Time Setup) + +If this is the first time setting up Release Please: + +```bash +# Bootstrap Release Please for the repository +npx release-please bootstrap \ + --repo-url=b-long/opentdf-python-sdk \ + --release-type=python + +# This creates the initial configuration files and release PR +``` + +### Preview Release Changes + +```bash +# See what Release Please would create (after bootstrap) +npx release-please release-pr \ + --repo-url=b-long/opentdf-python-sdk \ + --config-file=.release-please-config.json \ + --manifest-file=.release-please-manifest.json +``` + +### Manual Release Creation + +```bash +# Manually trigger GitHub release (if needed) +npx release-please github-release \ + --repo-url=b-long/opentdf-python-sdk \ + --config-file=.release-please-config.json \ + --manifest-file=.release-please-manifest.json +``` + +### Workflow Dispatch + +You can manually trigger releases via GitHub Actions: +- Go to Actions → "Release Please" → "Run workflow" +- Or Actions → "PyPIBuild" → "Run workflow" + +## Feature Branch Alpha Releases (For Testing) + +### When to Use Feature Branch Releases + +Use this approach when you need to test changes before merging to main: +- Testing breaking changes with external users +- Validating integration with downstream systems +- Providing preview releases for feedback + +### Process for Feature Branch Releases + +1. **Create Feature Branch**: + ```bash + git checkout -b feature/new-encryption-method + # Make your changes + git commit -m "feat: implement new encryption method" + ``` + +2. **Manually Bump Version** (create unique alpha version): + ```bash + # Option A: Use uv to bump version in pyproject.toml + uv version --bump alpha + + # Option B: Edit pyproject.toml directly to create unique alpha + # If current is 0.3.0a9, you might use 0.3.0a9.dev1 or 0.3.1a1 + ``` + +3. **Update Version Files**: + ```bash + # Update any version references in extra files + # (Release Please normally handles this) + sed -i 's/0.3.0a9/0.3.0a9.dev1/g' src/otdf_python/cli.py + ``` + +4. **Commit Version Changes**: + ```bash + git add . + git commit -m "chore: bump version for feature testing to 0.3.0a9.dev1" + ``` + +5. **Push Feature Branch**: + ```bash + git push origin feature/new-encryption-method + ``` + +6. **Manual Build and Publish**: + + **Option A: GitHub Actions (Recommended)** + - Push to a temporary branch that matches main branch patterns + - Or trigger workflow dispatch with your branch + + **Option B: Local Build** (for internal testing): + ```bash + # Build wheel locally + uv build + + # Install for testing + pip install dist/otdf_python-0.3.0a9.dev1-*.whl + + # Or upload to test.pypi.org manually + uv publish --repository testpypi dist/* + ``` + +### Feature Branch Naming Convention + +For feature branches that need releases, use clear naming: +- `feature/new-encryption-method` +- `experimental/performance-improvements` +- `preview/api-v2` + +### Cleanup After Feature Branch Testing + +```bash +# After merging feature to main, clean up +git branch -d feature/new-encryption-method +git push origin --delete feature/new-encryption-method + +# The main branch release will supersede the feature branch alpha +``` + +## Version Numbering Strategy + +### Alpha Versions +- **Sequential alphas**: `0.3.0a1`, `0.3.0a2`, `0.3.0a3`... +- **Feature branch alphas**: `0.3.0a9.dev1`, `0.3.1a1.dev1` +- **Experimental**: `0.4.0a1.experimental` + +### Stable Versions +- **Patch releases**: `0.3.0` → `0.3.1` (bug fixes) +- **Minor releases**: `0.3.0` → `0.4.0` (new features) +- **Major releases**: `0.3.0` → `1.0.0` (breaking changes) + +## Testing Release Candidates + +### Before Publishing +```bash +# Run full test suite +uv run pytest tests/ -v + +# Run integration tests +uv run pytest tests/ -m integration -v + +# Check code quality +uv run ruff check +uv run ruff format --check + +# Type checking (if configured) +uvx ty check src/ +``` + +### After Publishing +```bash +# Test installation from PyPI +pip install otdf-python==0.3.0a9 + +# Test basic functionality +python -c "import otdf_python; print('Import successful')" + +# Run smoke tests +uv run pytest tests/test_sdk.py::test_basic_functionality +``` + +## Troubleshooting Releases + +### Failed Test Suite +```bash +# Check what failed +uv run pytest tests/ -v --tb=short + +# Fix issues and re-run +uv run pytest tests/ -v +``` + +### Failed Build +- Check GitHub Actions logs +- Verify all platforms build successfully +- Ensure version format is correct + +### Failed PyPI Upload +- Verify PyPI trusted publisher setup +- Check for version conflicts +- Ensure all required metadata is present + +### Version Conflicts +```bash +# If version already exists on PyPI +uv version --bump patch # increment to next available version +``` + +## Multi-Package Releases + +This repository manages two packages: +- `otdf-python` (main SDK) +- `otdf-python-proto` (protobuf submodule) + +Both packages should maintain version sync. Release Please handles this automatically for main branch releases. + +## Security Considerations + +- Never commit API keys or credentials +- Trusted publishing prevents credential management +- All releases require passing security tests +- Alpha releases are publicly available on PyPI + +## Rollback Procedures + +### Yanking a Bad Release +```bash +# Yank from PyPI (makes it unavailable for new installs) +uv publish --yank "0.3.0a9" --reason "Critical security issue" + +# Create hotfix release +git checkout main +# Make fixes +git commit -m "fix: critical security issue" +# Follow normal release process +``` + +### Emergency Hotfix +```bash +# Create hotfix branch from last good release +git checkout v0.3.0 +git checkout -b hotfix/security-fix + +# Make minimal fix +git commit -m "fix: security vulnerability" + +# Merge back to main and release +git checkout main +git merge hotfix/security-fix +# Follow normal release process +``` + +## Release Checklist for Maintainers + +### Pre-Release +- [ ] All tests passing locally +- [ ] Integration tests passing +- [ ] Documentation updated +- [ ] CHANGELOG reviewed +- [ ] Version bump appropriate +- [ ] No security issues + +### During Release +- [ ] GitHub Actions tests pass +- [ ] Build artifacts created successfully +- [ ] PyPI upload successful +- [ ] GitHub release created + +### Post-Release +- [ ] Test installation from PyPI +- [ ] Verify SDK functionality +- [ ] Update any dependent projects +- [ ] Communicate release to users +- [ ] Monitor for issues + +## Support and Escalation + +For release issues: +1. Check GitHub Actions logs +2. Review PyPI trusted publisher setup +3. Verify release-please configuration +4. Contact repository maintainers +5. Create GitHub issue for persistent problems diff --git a/otdf-python-proto/README.md b/otdf-python-proto/README.md index 28d65f8..7327a9a 100644 --- a/otdf-python-proto/README.md +++ b/otdf-python-proto/README.md @@ -29,7 +29,7 @@ See [CONNECT_RPC_MIGRATION.md](../CONNECT_RPC_MIGRATION.md) for migration guide To generate Connect RPC clients and protobuf files: ```bash -cd proto-gen +cd otdf-python-proto uv run python scripts/generate_connect_proto.py ``` @@ -50,7 +50,7 @@ This generates: To generate traditional gRPC clients (backward compatibility): ```bash -cd proto-gen +cd otdf-python-proto uv run python scripts/generate_proto.py ``` @@ -65,7 +65,7 @@ Or use the legacy script: To download the latest proto files from OpenTDF platform: ```bash -cd proto-gen +cd otdf-python-proto uv run python scripts/generate_connect_proto.py --download ``` @@ -91,8 +91,8 @@ The generated files depend on: ```python import urllib3 -from generated.policy_pb2 import GetPolicyRequest -from generated.policy_connect import PolicyServiceClient +from otdf_python_proto.policy_pb2 import GetPolicyRequest +from otdf_python_proto.policy_connect import PolicyServiceClient # Create HTTP client http_client = urllib3.PoolManager() @@ -116,7 +116,7 @@ response = client.get_policy( ```python import aiohttp -from generated.policy_connect import AsyncPolicyServiceClient +from otdf_python_proto.policy_connect import AsyncPolicyServiceClient async with aiohttp.ClientSession() as session: client = AsyncPolicyServiceClient( @@ -131,7 +131,7 @@ async with aiohttp.ClientSession() as session: ```python import grpc -from generated.legacy_grpc.policy_pb2_grpc import PolicyServiceStub +from otdf_python_proto.legacy_grpc.policy_pb2_grpc import PolicyServiceStub channel = grpc.insecure_channel("platform.opentdf.io:443") client = PolicyServiceStub(channel) @@ -159,7 +159,7 @@ response = client.GetPolicy(request) If you're migrating from traditional gRPC clients to Connect RPC: 1. Read the [Connect RPC Migration Guide](../CONNECT_RPC_MIGRATION.md) -2. Run the Connect RPC generation: `./scripts/build_connect_proto.sh` +2. Run the Connect RPC generation: `./scripts/build_connect_proto.sh` (or from the submodule: `cd otdf-python-proto && uv run python scripts/generate_connect_proto.py`) 3. Update your client code to use `*_connect.py` modules 4. Test with your authentication and deployment setup 5. Optionally remove legacy gRPC dependencies @@ -173,7 +173,7 @@ Install buf: `brew install bufbuild/buf/buf` Install with compiler support: `uv add connect-python[compiler]` ### Import errors after generation -Ensure `__init__.py` files exist in generated directories +Ensure `__init__.py` files exist in otdf_python_proto directories ### Protocol version mismatches Regenerate with latest proto files: `uv run python scripts/generate_connect_proto.py --download` diff --git a/otdf-python-proto/pyproject.toml b/otdf-python-proto/pyproject.toml index 883b676..e9ba73b 100644 --- a/otdf-python-proto/pyproject.toml +++ b/otdf-python-proto/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "otdf-python-proto" -version = "0.3.0a6" +version = "0.3.0a9" description = "Generated protobuf files for OpenTDF Python SDK" readme = "README.md" authors = [ diff --git a/pyproject.toml b/pyproject.toml index 9ac1cf1..8379b7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "otdf-python" -version = "0.3.0a6" +version = "0.3.0a9" description = "Unofficial OpenTDF SDK for Python" readme = "README.md" authors = [ @@ -37,6 +37,7 @@ dev = [ "pydantic-settings>=2.10.1", "pytest>=8.4.1", "respx>=0.21.1", + "ruff>=0.12.10", ] [tool.pytest.ini_options] @@ -76,3 +77,5 @@ lint.select = [ "FURB", # refurb (FURB) "PT018", # flake8-pytest-style (pytest style) ] +# Ignore generated files +extend-exclude = ["otdf-python-proto/src/"] diff --git a/src/otdf_python/cli.py b/src/otdf_python/cli.py index 20bcb17..de3bb9c 100644 --- a/src/otdf_python/cli.py +++ b/src/otdf_python/cli.py @@ -22,7 +22,7 @@ # Version - get from project metadata -__version__ = "0.3.0a6" +__version__ = "0.3.0a9" # Set up logging diff --git a/tests/test_cli.py b/tests/test_cli.py index 7dcf4d8..c31a6c0 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -35,7 +35,7 @@ def test_cli_version(): ) assert result.returncode == 0 assert "OpenTDF Python SDK" in result.stdout - assert "0.3.0a6" in result.stdout + assert "0.3.0a9" in result.stdout def test_cli_encrypt_help(): diff --git a/uv.lock b/uv.lock index fc85496..1529756 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.10" [[package]] @@ -686,7 +686,7 @@ wheels = [ [[package]] name = "otdf-python" -version = "0.3.0a6" +version = "0.3.0a9" source = { editable = "." } dependencies = [ { name = "connect-python", extra = ["compiler"] }, @@ -706,6 +706,7 @@ dev = [ { name = "pydantic-settings" }, { name = "pytest" }, { name = "respx" }, + { name = "ruff" }, ] [package.metadata] @@ -727,6 +728,7 @@ dev = [ { name = "pydantic-settings", specifier = ">=2.10.1" }, { name = "pytest", specifier = ">=8.4.1" }, { name = "respx", specifier = ">=0.21.1" }, + { name = "ruff", specifier = ">=0.12.10" }, ] [[package]] @@ -1057,6 +1059,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/67/afbb0978d5399bc9ea200f1d4489a23c9a1dad4eee6376242b8182389c79/respx-0.22.0-py2.py3-none-any.whl", hash = "sha256:631128d4c9aba15e56903fb5f66fb1eff412ce28dd387ca3a81339e52dbd3ad0", size = 25127, upload-time = "2024-12-19T22:33:57.837Z" }, ] +[[package]] +name = "ruff" +version = "0.12.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/eb/8c073deb376e46ae767f4961390d17545e8535921d2f65101720ed8bd434/ruff-0.12.10.tar.gz", hash = "sha256:189ab65149d11ea69a2d775343adf5f49bb2426fc4780f65ee33b423ad2e47f9", size = 5310076, upload-time = "2025-08-21T18:23:22.595Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/e7/560d049d15585d6c201f9eeacd2fd130def3741323e5ccf123786e0e3c95/ruff-0.12.10-py3-none-linux_armv6l.whl", hash = "sha256:8b593cb0fb55cc8692dac7b06deb29afda78c721c7ccfed22db941201b7b8f7b", size = 11935161, upload-time = "2025-08-21T18:22:26.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b0/ad2464922a1113c365d12b8f80ed70fcfb39764288ac77c995156080488d/ruff-0.12.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1", size = 12660884, upload-time = "2025-08-21T18:22:30.925Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f1/97f509b4108d7bae16c48389f54f005b62ce86712120fd8b2d8e88a7cb49/ruff-0.12.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d59e58586829f8e4a9920788f6efba97a13d1fa320b047814e8afede381c6839", size = 11872754, upload-time = "2025-08-21T18:22:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/12/ad/44f606d243f744a75adc432275217296095101f83f966842063d78eee2d3/ruff-0.12.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:822d9677b560f1fdeab69b89d1f444bf5459da4aa04e06e766cf0121771ab844", size = 12092276, upload-time = "2025-08-21T18:22:36.764Z" }, + { url = "https://files.pythonhosted.org/packages/06/1f/ed6c265e199568010197909b25c896d66e4ef2c5e1c3808caf461f6f3579/ruff-0.12.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b4a64f4062a50c75019c61c7017ff598cb444984b638511f48539d3a1c98db", size = 11734700, upload-time = "2025-08-21T18:22:39.822Z" }, + { url = "https://files.pythonhosted.org/packages/63/c5/b21cde720f54a1d1db71538c0bc9b73dee4b563a7dd7d2e404914904d7f5/ruff-0.12.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6f4064c69d2542029b2a61d39920c85240c39837599d7f2e32e80d36401d6e", size = 13468783, upload-time = "2025-08-21T18:22:42.559Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/39369e6ac7f2a1848f22fb0b00b690492f20811a1ac5c1fd1d2798329263/ruff-0.12.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559", size = 14436642, upload-time = "2025-08-21T18:22:45.612Z" }, + { url = "https://files.pythonhosted.org/packages/e3/03/5da8cad4b0d5242a936eb203b58318016db44f5c5d351b07e3f5e211bb89/ruff-0.12.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bef6161e297c68908b7218fa6e0e93e99a286e5ed9653d4be71e687dff101cf", size = 13859107, upload-time = "2025-08-21T18:22:48.886Z" }, + { url = "https://files.pythonhosted.org/packages/19/19/dd7273b69bf7f93a070c9cec9494a94048325ad18fdcf50114f07e6bf417/ruff-0.12.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f1345fbf8fb0531cd722285b5f15af49b2932742fc96b633e883da8d841896b", size = 12886521, upload-time = "2025-08-21T18:22:51.567Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1d/b4207ec35e7babaee62c462769e77457e26eb853fbdc877af29417033333/ruff-0.12.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f68433c4fbc63efbfa3ba5db31727db229fa4e61000f452c540474b03de52a9", size = 13097528, upload-time = "2025-08-21T18:22:54.609Z" }, + { url = "https://files.pythonhosted.org/packages/ff/00/58f7b873b21114456e880b75176af3490d7a2836033779ca42f50de3b47a/ruff-0.12.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a", size = 13080443, upload-time = "2025-08-21T18:22:57.413Z" }, + { url = "https://files.pythonhosted.org/packages/12/8c/9e6660007fb10189ccb78a02b41691288038e51e4788bf49b0a60f740604/ruff-0.12.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60", size = 11896759, upload-time = "2025-08-21T18:23:00.473Z" }, + { url = "https://files.pythonhosted.org/packages/67/4c/6d092bb99ea9ea6ebda817a0e7ad886f42a58b4501a7e27cd97371d0ba54/ruff-0.12.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7d1a4e0bdfafcd2e3e235ecf50bf0176f74dd37902f241588ae1f6c827a36c56", size = 11701463, upload-time = "2025-08-21T18:23:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/59/80/d982c55e91df981f3ab62559371380616c57ffd0172d96850280c2b04fa8/ruff-0.12.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9", size = 12691603, upload-time = "2025-08-21T18:23:06.935Z" }, + { url = "https://files.pythonhosted.org/packages/ad/37/63a9c788bbe0b0850611669ec6b8589838faf2f4f959647f2d3e320383ae/ruff-0.12.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ae479e1a18b439c59138f066ae79cc0f3ee250712a873d00dbafadaad9481e5b", size = 13164356, upload-time = "2025-08-21T18:23:10.225Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/1aaa7fb201a74181989970ebccd12f88c0fc074777027e2a21de5a90657e/ruff-0.12.10-py3-none-win32.whl", hash = "sha256:9de785e95dc2f09846c5e6e1d3a3d32ecd0b283a979898ad427a9be7be22b266", size = 11896089, upload-time = "2025-08-21T18:23:14.232Z" }, + { url = "https://files.pythonhosted.org/packages/ad/14/2ad38fd4037daab9e023456a4a40ed0154e9971f8d6aed41bdea390aabd9/ruff-0.12.10-py3-none-win_amd64.whl", hash = "sha256:7837eca8787f076f67aba2ca559cefd9c5cbc3a9852fd66186f4201b87c1563e", size = 13004616, upload-time = "2025-08-21T18:23:17.422Z" }, + { url = "https://files.pythonhosted.org/packages/24/3c/21cf283d67af33a8e6ed242396863af195a8a6134ec581524fd22b9811b6/ruff-0.12.10-py3-none-win_arm64.whl", hash = "sha256:cc138cc06ed9d4bfa9d667a65af7172b47840e1a98b02ce7011c391e54635ffc", size = 12074225, upload-time = "2025-08-21T18:23:20.137Z" }, +] + [[package]] name = "setuptools" version = "80.9.0"