Skip to content

Implement Python SDK for BundleUp#1

Merged
sonnyt merged 5 commits intomainfrom
copilot/create-python-sdk-bundleup
Jan 22, 2026
Merged

Implement Python SDK for BundleUp#1
sonnyt merged 5 commits intomainfrom
copilot/create-python-sdk-bundleup

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 21, 2026

Created Python SDK for BundleUp with Python best practices. Package installable as bundleup-sdk with Python 3.8+ support.

Core Components

  • BundleUp - Main client with api_key auth

    • Properties: connections, integrations, webhooks
    • Methods: proxy(connection_id), unify(connection_id)
    • Context manager support for automatic resource cleanup
    • Optional custom requests.Session for connection pooling
  • Base - Abstract resource class with generic types

    • CRUD: list(), create(), retrieve(), update(), delete()
    • Base URL: https://api.bundleup.io/v1
    • Centralized error handling with custom exceptions
    • Query parameters support in list() method
  • Resources - Connections, Integrations, Webhooks extending Base

  • Proxy - Direct API calls to connected services

    • Base URL: https://proxy.bundleup.io
    • Methods: get(), post(), put(), patch(), delete()
    • Injects BU-Connection-Id header
    • Supports **kwargs for flexibility
  • Unify - Standardized API with pagination support

    • Base URL: https://unify.bundleup.io
    • Chat.channels(), Git.repos/pulls/tags/releases(), PM.issues()
    • Params: limit, after, include_raw
  • Custom Exception Hierarchy

    • BundleUpError - Base exception
    • ValidationError - Input validation errors
    • APIError - General API errors with status code and response body
    • AuthenticationError - 401 errors
    • NotFoundError - 404 errors
    • RateLimitError - 429 errors

Usage

from bundleup import BundleUp

# Context manager (recommended)
with BundleUp("api-key") as client:
    connections = client.connections.list()
    conn = client.connections.create({"name": "My Connection"})

# Or regular usage
client = BundleUp("api-key")
connections = client.connections.list()

# Proxy for direct API calls
proxy = client.proxy("connection-id")
users = proxy.get("/users")

# Unified API
channels = client.unify("connection-id").chat.channels({"limit": 50})
repos = client.unify("connection-id").git.repos()

# Error handling
from bundleup.exceptions import AuthenticationError, NotFoundError

try:
    client.connections.retrieve("id")
except NotFoundError:
    print("Connection not found")

Python Best Practices

  • Type hints throughout (TypedDict for resources, generics for Base)
  • Context manager support for automatic resource cleanup
  • Custom exception hierarchy for better error handling
  • Connection pooling with requests.Session
  • @abstractmethod decorators for abstract properties
  • __repr__ methods for better debugging
  • Input validation with descriptive errors
  • requests for HTTP, typing-extensions for compatibility
  • 70 unit tests with mocked requests
  • CI/CD for Python 3.8-3.12
Original prompt

Create Python SDK for BundleUp

Create a complete Python SDK for BundleUp that mirrors the functionality and structure of the JavaScript SDK from bundleup/javascript repository.

Reference Implementation

The JavaScript SDK is located at bundleup/javascript in the packages/sdk directory.

Required Structure

Main Files to Create

  1. bundleup/__init__.py - Main SDK class

    • BundleUp class with api_key parameter in constructor
    • Properties: connections, integrations, webhooks
    • Methods: proxy(connection_id), unify(connection_id)
  2. bundleup/base.py - Base class for API resources

    • Abstract Base class with generic type support
    • Protected namespace property
    • base_url = "https://api.bundleup.io"
    • version = "v1"
    • Methods: list(), create(), retrieve(), update(), del()
    • Protected methods: _build_url(), _headers property
  3. bundleup/connection.py - Connections resource

    • Connection TypedDict/dataclass
    • Connections class extending Base
  4. bundleup/integration.py - Integrations resource

    • Integration TypedDict/dataclass
    • Integrations class extending Base
  5. bundleup/webhooks.py - Webhooks resource

    • Webhook TypedDict/dataclass
    • Webhooks class extending Base
  6. bundleup/proxy.py - Proxy class for direct API calls

    • base_url = "https://proxy.bundleup.io"
    • Methods: get(), post(), put(), patch(), delete()
    • Custom headers including BU-Connection-Id
  7. bundleup/unify/base.py - Base class for Unify API

    • base_url = "https://unify.bundleup.io"
    • Params TypedDict with limit, after, include_raw
    • Response TypedDict with data, _raw, metadata
  8. bundleup/unify/chat.py - Chat unify methods

    • Chat class with channels() method
  9. bundleup/unify/git.py - Git unify methods

    • Git class with methods: repos(), pulls(), tags(), releases()
  10. bundleup/unify/pm.py - Project Management unify methods

    • PM class with issues() method
  11. bundleup/utils.py - Utility functions

    • Helper functions as needed

Additional Files

  • setup.py or pyproject.toml - Package configuration

    • Package name: bundleup-sdk
    • Author: BundleUp
    • License: ISC
    • Python version: >=3.8
  • README.md - Documentation

    • Installation instructions
    • Basic usage examples
    • Links to documentation
  • requirements.txt - Dependencies

    • requests or httpx for HTTP calls
    • typing-extensions for better type hints
  • tests/ directory - Unit tests

    • Test files for each module

Implementation Requirements

  1. Use type hints throughout (compatible with Python 3.8+)
  2. Use requests library for HTTP calls (similar to fetch in JS)
  3. Follow PEP 8 style guidelines
  4. Include docstrings for all public methods
  5. Handle errors appropriately (raise exceptions similar to JS version)
  6. Validate inputs (check if objects are dicts, strings are not empty, etc.)
  7. Use property decorators for resource accessors
  8. Authentication via Bearer token in Authorization header

Error Handling

  • Raise ValueError for invalid parameters
  • Raise RuntimeError or custom exceptions for API errors
  • Include error messages from API responses

Example Usage Pattern (should work like this)

from bundleup import BundleUp

# Initialize client
client = BundleUp("your-api-key")

# Access resources
connections = client.connections.list()
integrations = client.integrations.list()
webhooks = client.webhooks.list()

# Use proxy
proxy = client.proxy("connection-id")
response = proxy.get("/some/path")

# Use unify API
channels = client.unify("connection-id").chat.channels()
repos = client.unify("connection-id").git.repos()
issues = client.unify("connection-id").pm.issues()

Notes

  • The SDK should be installable via pip
  • Follow the same API structure and method signatures as the JavaScript version
  • Maintain the same base URLs for different services
  • Use snake_case for Python (e.g., connection_id instead of connectionId)
  • Convert camelCase from JS to snake_case in Python where appropriate

This pull request was created from Copilot chat.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 3 commits January 21, 2026 16:25
Co-authored-by: sonnyt <183387+sonnyt@users.noreply.github.com>
Co-authored-by: sonnyt <183387+sonnyt@users.noreply.github.com>
Co-authored-by: sonnyt <183387+sonnyt@users.noreply.github.com>
Copilot AI changed the title [WIP] Add Python SDK for BundleUp Implement Python SDK for BundleUp Jan 21, 2026
Copilot AI requested a review from sonnyt January 21, 2026 16:32
@sonnyt
Copy link
Copy Markdown
Contributor

sonnyt commented Jan 21, 2026

@copilot create Ruby sdk. Use ruby best practises

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 21, 2026

@copilot create Ruby sdk. Use ruby best practises

I'd be happy to help create a Ruby SDK! However, this is the bundleup-sdk-python repository. Should I:

  1. Create the Ruby SDK in a new/separate repository (e.g., bundleup-sdk-ruby)?
  2. Or did you want me to work in a different repository that already exists?

Creating a Ruby SDK in this Python repository wouldn't follow best practices for language-specific SDK repositories. Please let me know where you'd like the Ruby SDK to be created.

@sonnyt
Copy link
Copy Markdown
Contributor

sonnyt commented Jan 21, 2026

@copilot use the existing bundleup-sdk-ruby repo

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 21, 2026

@copilot use the existing bundleup-sdk-ruby repo

I cannot access the bundleup-sdk-ruby repository from this context. I can only work within the current repository (bundleup-sdk-python).

To create the Ruby SDK, please create an issue or PR in the bundleup-sdk-ruby repository and tag me there. I'll be able to help once you open a conversation in that repository.

@sonnyt
Copy link
Copy Markdown
Contributor

sonnyt commented Jan 21, 2026

@copilot use python best practicies. Implementation does not need to match JS identically.

…sion pooling, and __repr__ methods

Co-authored-by: sonnyt <183387+sonnyt@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 21, 2026

@copilot use python best practicies. Implementation does not need to match JS identically.

Applied Python best practices in commit 42a7123:

Custom Exception Hierarchy:

  • BundleUpError base class
  • ValidationError, APIError, AuthenticationError, NotFoundError, RateLimitError
  • Exceptions include status codes and response bodies

Context Manager Support:

with BundleUp("api-key") as client:
    connections = client.connections.list()
# Automatically closes session

Connection Pooling:

  • Uses requests.Session for efficient HTTP connection reuse
  • Optional custom session support

Better Error Handling:

  • Centralized response handling with specific exceptions for 401, 404, 429
  • More informative error messages

Python Idioms:

  • @abstractmethod decorator for abstract properties
  • __repr__ methods for better debugging
  • **kwargs support in HTTP methods for flexibility
  • Query parameters support in list() method

All 70 tests passing.

@sonnyt sonnyt marked this pull request as ready for review January 22, 2026 01:57
@sonnyt sonnyt merged commit 91c20b3 into main Jan 22, 2026
7 checks passed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 42a712307e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +112 to +116
try:
error_body = response.text if hasattr(response, 'text') else None
except:
error_body = None
raise APIError(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard against missing response in Unify request errors

If the HTTP request fails before a response object is created (e.g., DNS failure, connection refused, or timeout), requests.get raises a RequestException and response is never assigned. The exception handler then dereferences response to read response.text, which raises UnboundLocalError and masks the original network failure, so callers never see the intended APIError. Initialize response = None before the try, or use getattr(e, "response", None) to avoid crashing on connection-level failures.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants