Skip to content

Commit

Permalink
Sm 989 create build pipeline for the python language wrapper (#369)
Browse files Browse the repository at this point in the history
## Type of change

- [ ] Bug fix
- [ ] New feature development
- [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [x] Build/deploy pipeline (DevOps)
- [ ] Other

## Objective

Package the Python SDK so that it can be published to
[PyPI.org](pypi.org).

## Code changes

- **`.github/workflows/build-python-wheels.yml`:** create wheels for
Linux, macOS, and Windows (AMD64-only). A sdist is also created for
other platforms.
- **`languages/python/BitwardenClient/__init__.py`:** export functions
and classes for autocompletion in IDEs and the interactive Python
interpreter
- **`languages/python/pyproject.toml`**: replaces `setup.py`, which is
no longer officially supported. This uses Maturin to build the Rust
shared-object library that will be bundled with the SDK.
- **`languages/python/setup.py`**: replaced by `pyproject.toml`

## Before you submit

- Please add **unit tests** where it makes sense to do so (encouraged
but not required)

---------

Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
  • Loading branch information
tangowithfoxtrot and dani-garcia authored Jan 18, 2024
1 parent d70deac commit 9cad470
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 50 deletions.
122 changes: 122 additions & 0 deletions .github/workflows/build-python-wheels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
---
name: Build Python Wheels

on:
pull_request:
push:
branches:
- "main"
- "rc"
- "hotfix-rc"
workflow_dispatch:

defaults:
run:
shell: bash
working-directory: languages/python

jobs:
generate_schemas:
uses: ./.github/workflows/generate_schemas.yml

setup:
name: Setup
runs-on: ubuntu-22.04
outputs:
package_version: ${{ steps.retrieve-version.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

- name: Get Package Version
id: retrieve-version
run: |
VERSION="$(grep -o '^version = ".*"' ../../crates/bitwarden-py/Cargo.toml | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+")"
echo "package_version=$VERSION" >> $GITHUB_OUTPUT
build:
name: Building Python wheel for - ${{ matrix.settings.os }} - ${{ matrix.settings.target }}
runs-on: ${{ matrix.settings.os || 'ubuntu-latest' }}
needs:
- generate_schemas
- setup
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
strategy:
fail-fast: false
matrix:
settings:
- os: macos-12
target: x86_64-apple-darwin

- os: macos-12
target: aarch64-apple-darwin

- os: windows-2022
target: x86_64-pc-windows-msvc

- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu

- os: ubuntu-22.04
target: aarch64-unknown-linux-gnu

steps:
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

- name: Setup Node
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
with:
node-version: 18

- name: Install rust
uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable
with:
toolchain: stable
targets: ${{ matrix.settings.target }}

- name: Cache cargo registry
uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1
with:
key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.os }}

- name: Retrieve schemas
uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0
with:
name: schemas.py
path: ${{ github.workspace }}/languages/python/bitwarden_sdk

- name: Build wheels
if: ${{ matrix.settings.target != 'x86_64-unknown-linux-gnu' }}
uses: PyO3/maturin-action@b9e8f88fd4448fdecf5095864cdc7e39a544aa9f # v1.40.7
with:
target: ${{ matrix.settings.target }}
args: --release --find-interpreter --sdist
sccache: "true"
manylinux: "2_28" # https://github.com/pola-rs/polars/pull/12211
working-directory: ${{ github.workspace }}/languages/python

- name: Build wheels (Linux - x86_64)
if: ${{ matrix.settings.target == 'x86_64-unknown-linux-gnu' }}
uses: PyO3/maturin-action@b9e8f88fd4448fdecf5095864cdc7e39a544aa9f # v1.40.7
with:
target: ${{ matrix.settings.target }}
args: --release --find-interpreter --sdist
container: quay.io/pypa/manylinux_2_28_x86_64:2023-11-20-745eb52
sccache: "true"
manylinux: "2_28" # https://github.com/pola-rs/polars/pull/12211
working-directory: ${{ github.workspace }}/languages/python

- name: Upload wheels
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: bitwarden_sdk-${{ env._PACKAGE_VERSION }}-${{ matrix.settings.target }}
path: ${{ github.workspace }}/target/wheels/bitwarden_sdk*.whl

- name: Upload sdists
if: ${{ matrix.settings.target == 'x86_64-unknown-linux-gnu' }} # we only need one sdist
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: bitwarden_sdk-${{ env._PACKAGE_VERSION }}-sdist
path: ${{ github.workspace }}/target/wheels/bitwarden_sdk-*.tar.gz
45 changes: 23 additions & 22 deletions languages/python/README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
# Requirements

- Python3
- setuptools
```bash
pip install setuptools
```
- setuptools_rust
```bash
pip install setuptools_rust
```
- dateutil
```bash
pip install python-dateutil
```

# Installation

From the `languages/python/` directory,
# Build locally
## Requirements

- Python 3
- `maturin` (install with `pip install maturin[patchelf]`)
- `npm`

## Build

From the root of the repository:
```bash
python3 ./setup.py develop
npm run schemas # generate schemas.py

cd languages/python/
maturin develop
```

Rename the the resulting `.so` file to `bitwarden_py.so`, if it isn't already there.
You can now import `BitwardenClient` in your Python code.

# Use without building locally

```bash
pip install BitwardenClient
```

# Run

Set the `ORGANIZATION_ID` and `ACCESS_TOKEN` environment variables to your organization ID and access token, respectively.

```bash
python3 ./login.py
python3 ./example.py
```
13 changes: 13 additions & 0 deletions languages/python/bitwarden_sdk/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""The official Bitwarden client library for Python."""

__version__ = "0.1.0"

from .bitwarden_client import *
from .schemas import *

__doc__ = bitwarden_client.__doc__
if hasattr(bitwarden_client, "__all__"):
__all__ = bitwarden_client.__all__

if hasattr(schemas, "__all__"):
__all__ += schemas.__all__
57 changes: 41 additions & 16 deletions languages/python/example.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
import json
#!/usr/bin/env python3
import logging
import sys
from bitwarden_sdk.bitwarden_client import BitwardenClient
from bitwarden_sdk.schemas import client_settings_from_dict, DeviceType
import os

from bitwarden_sdk import BitwardenClient, DeviceType, client_settings_from_dict

# Create the BitwardenClient, which is used to interact with the SDK
client = BitwardenClient(client_settings_from_dict({
"apiUrl": "http://localhost:4000",
"deviceType": DeviceType.SDK,
"identityUrl": "http://localhost:33656",
"userAgent": "Python",
}))
client = BitwardenClient(
client_settings_from_dict(
{
"apiUrl": os.getenv("API_URL", "http://localhost:4000"),
"deviceType": DeviceType.SDK,
"identityUrl": os.getenv("IDENTITY_URL", "http://localhost:33656"),
"userAgent": "Python",
}
)
)

# Add some logging & set the org id
logging.basicConfig(level=logging.DEBUG)
organization_id = "org_id_here"
organization_id = os.getenv("ORGANIZATION_ID")

# Attempt to authenticate with the Secrets Manager Access Token
client.access_token_login("access_token_here")
client.access_token_login(os.getenv("ACCESS_TOKEN"))

# -- Example Project Commands --

project = client.projects().create("ProjectName", organization_id)
project2 = client.projects().create("Project - Don't Delete Me!", organization_id)
updated_project = client.projects().update(project.data.id, "Cool New Project Name", organization_id)
updated_project = client.projects().update(
project.data.id, "Cool New Project Name", organization_id
)
get_that_project = client.projects().get(project.data.id)

input("Press Enter to delete the project...")
Expand All @@ -33,9 +39,28 @@

# -- Example Secret Commands --

secret = client.secrets().create("TEST_SECRET", "This is a test secret", organization_id, "Secret1234!", [project2.data.id])
secret2 = client.secrets().create("Secret - Don't Delete Me!", "This is a test secret that will stay", organization_id, "Secret1234!", [project2.data.id])
secret_updated = client.secrets().update(secret.data.id, "TEST_SECRET_UPDATED", "This as an updated test secret", organization_id, "Secret1234!_updated", [project2.data.id])
secret = client.secrets().create(
"TEST_SECRET",
"This is a test secret",
organization_id,
"Secret1234!",
[project2.data.id],
)
secret2 = client.secrets().create(
"Secret - Don't Delete Me!",
"This is a test secret that will stay",
organization_id,
"Secret1234!",
[project2.data.id],
)
secret_updated = client.secrets().update(
secret.data.id,
"TEST_SECRET_UPDATED",
"This as an updated test secret",
organization_id,
"Secret1234!_updated",
[project2.data.id],
)
secret_retrieved = client.secrets().get(secret.data.id)

input("Press Enter to delete the secret...")
Expand Down
29 changes: 29 additions & 0 deletions languages/python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[build-system]
build-backend = "maturin"
requires = ["maturin>=1.0,<2.0", "setuptools_rust>=1.8.1"]

[project]
authors = [{ name = "Bitwarden", email = "support@bitwarden.com" }]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: Other/Proprietary License",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Rust",
"Topic :: Security",
]
dependencies = ["dateutils >= 0.6.6"]
description = "A Bitwarden Client for python"
name = "bitwarden_sdk"
readme = "README.md"
requires-python = ">=3.0"
version = "0.1.0"

[tool.maturin]
bindings = "pyo3"
compatibility = "2_28"
include = [
{ path = "bitwarden_sdk/*.py", format = ["sdist", "wheel"] }
]
manifest-path = "../../crates/bitwarden-py/Cargo.toml"
python-packages = ["bitwarden_sdk"]
12 changes: 0 additions & 12 deletions languages/python/setup.py

This file was deleted.

0 comments on commit 9cad470

Please sign in to comment.