Skip to content

feat: add Application resource support#158

Draft
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
cdrappier/app-runtime
Draft

feat: add Application resource support#158
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
cdrappier/app-runtime

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Fixes ENG-2932

Summary

Adds ApplicationInstance and SyncApplicationInstance CRUD helpers for the new Application resource, following the same pattern as DriveInstance/VolumeInstance.

Step 1 – Client regeneration: Regenerated the controlplane client from the cdrappier/application-runtime-pr2-api branch spec. This pulls in Application, ApplicationSpec, ApplicationList models and create/get/list/delete/update_application + list_application_revisions API functions, plus other new models from the updated spec (pagination, firewall, HIPAA, etc.).

Step 2 – Application helper (src/blaxel/core/application/):

# Async
app = await ApplicationInstance.create({"name": "my-app", "region": "us-pdx-1"})
app = await ApplicationInstance.get("my-app")
apps = await ApplicationInstance.list()
await app.delete()
app = await app.update({"display_name": "New Name"})
app = await ApplicationInstance.create_if_not_exists({...})

# Sync
app = SyncApplicationInstance.create({"name": "my-app", "region": "us-pdx-1"})

Key design choices:

  • ApplicationCreateConfiguration accepts name, display_name, labels, image, region, enabled
  • list() handles the paginated ApplicationList response (extracts .data)
  • Delete/update use the same descriptor pattern (_AsyncDeleteDescriptor / _SyncUpdateDescriptor) for class-level and instance-level calls
  • Region warning matches Drive/Volume behavior

Step 3 – Exports: Re-exported ApplicationInstance, SyncApplicationInstance, ApplicationCreateConfiguration, ApplicationAPIError from blaxel.core.

Link to Devin session: https://app.devin.ai/sessions/7bcfb61fc2a0401587b0faa456cbe08d
Requested by: @drappier-charles


Note

Adds Application resource CRUD helpers (async + sync), regenerates controlplane client with pagination support, and handles bare-array backward compatibility for list endpoints.

Written by Mendral for commit 527007f.

- Regenerate controlplane client from cdrappier/application-runtime-pr2-api branch spec
- Add Application models (Application, ApplicationSpec, ApplicationList) and API functions (CRUD + list revisions)
- Create ApplicationInstance (async) and SyncApplicationInstance (sync) helper classes
- Support create, get, list, delete, update, and create_if_not_exists operations
- Export from blaxel.core.application and re-export from blaxel.core

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@mendral-app

mendral-app Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

✅ Linked to Linear issue ENG-2932 — status set to In Progress

  • Assignee: Charles Drappier (@drappier-charles)
  • Match: This PR implements exactly the Python SDK regeneration + ApplicationInstance DX helpers described in ENG-2932
  • PR linked: ✅ Issue will auto-close when this PR merges

Linear reference Fixes ENG-2932 prepended to PR description.

Note

Posted by Linear Issue Enforcer · Tag @mendral-app with feedback.

@mendral-app

mendral-app Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🔀 Component Interaction Diagram

Here's a sequence diagram showing the key interactions introduced by this PR:

sequenceDiagram
    participant User as User Code
    participant AI as ApplicationInstance<br/>(Async/Sync)
    participant Config as ApplicationCreate<br/>Configuration
    participant API as API Endpoint Functions<br/>(create/get/list/update/delete)
    participant HTTP as HTTPX Client
    participant CP as Controlplane API

    Note over User, CP: CREATE Flow
    User->>AI: ApplicationInstance.create(config)
    AI->>Config: Normalize input (dict/config/Application)
    Config-->>AI: ApplicationCreateConfiguration
    AI->>AI: Build Application model (Metadata + ApplicationSpec)
    AI->>API: create_application(client, body)
    API->>HTTP: POST /applications
    HTTP->>CP: HTTP Request
    CP-->>HTTP: 200 Application / 409 Error
    HTTP-->>API: Response
    API-->>AI: Application or Error
    AI-->>User: ApplicationInstance (wrapped)

    Note over User, CP: GET Flow
    User->>AI: ApplicationInstance.get(name)
    AI->>API: get_application(name, client)
    API->>HTTP: GET /applications/{name}
    HTTP->>CP: HTTP Request
    CP-->>HTTP: 200 Application / 404 Error
    API-->>AI: Application or Error
    AI-->>User: ApplicationInstance (wrapped)

    Note over User, CP: UPDATE Flow (via Descriptor)
    User->>AI: instance.update(changes)
    AI->>AI: _AsyncUpdateDescriptor.__get__()
    AI->>API: get_application (fetch current state)
    API->>HTTP: GET /applications/{name}
    HTTP-->>API: Current Application
    AI->>AI: Merge changes with current state
    AI->>API: update_application(name, client, merged_body)
    API->>HTTP: PUT /applications/{name}
    HTTP->>CP: HTTP Request
    CP-->>API: Updated Application
    API-->>AI: Application
    AI-->>User: New ApplicationInstance

    Note over User, CP: CREATE_IF_NOT_EXISTS Flow
    User->>AI: ApplicationInstance.create_if_not_exists(config)
    AI->>API: create_application(client, body)
    API->>HTTP: POST /applications
    HTTP->>CP: HTTP Request
    alt Success (200)
        CP-->>AI: Application
        AI-->>User: ApplicationInstance
    else Conflict (409)
        CP-->>AI: Error (APPLICATION_ALREADY_EXISTS)
        AI->>API: get_application(name, client)
        API->>HTTP: GET /applications/{name}
        CP-->>AI: Existing Application
        AI-->>User: ApplicationInstance (existing)
    end
Loading

Summary of the Flow

This PR adds full CRUD support for the Application resource, following the existing SDK patterns (similar to DriveInstance/VolumeInstance):

Layer Role
ApplicationInstance / SyncApplicationInstance Public-facing wrapper with convenience properties and classmethods
ApplicationCreateConfiguration Normalizes user input (dict, config, or raw model) into a valid Application object
API Endpoint Functions Generated from OpenAPI spec — handles HTTP request building and response parsing
HTTPX Client Executes HTTP calls against the Controlplane API

Key design choices:

  • Descriptor pattern for delete/update — enables both Class.method(name) and instance.method() usage
  • Merge-on-update — fetches current state before applying partial updates to avoid field loss
  • Dual async/sync — consistent interface for both execution models
  • Auto-generated names — defaults to app-{uuid[:8]} if no name is provided

Note

Posted by PR Sequence Diagram · Tag @mendral-app with feedback.

@mendral-app

mendral-app Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🧪 Testing Guide

What this PR addresses

Adds a new Application resource to the SDK with full CRUD support via ApplicationInstance (async) and SyncApplicationInstance (sync) helper classes, following the same pattern as the existing DriveInstance/VolumeInstance. Also regenerates the controlplane client to include pagination enhancements for existing list operations.

Steps to exercise the new behavior

  1. Import the new classes from the blaxel.core package:

    from blaxel.core import (
        ApplicationInstance,
        SyncApplicationInstance,
        ApplicationCreateConfiguration,
        ApplicationAPIError,
    )
  2. Create an application (async):

    app = await ApplicationInstance.create({"name": "test-app", "region": "us-pdx-1", "enabled": True})
  3. Get an application by name:

    app = await ApplicationInstance.get("test-app")
  4. List applications:

    apps = await ApplicationInstance.list()
  5. Update an application (instance-level and class-level):

    updated = await app.update({"display_name": "Updated Name"})
    # or class-level:
    updated = await ApplicationInstance.update("test-app", {"display_name": "Updated Name"})
  6. Delete an application:

    await app.delete()
    # or class-level:
    await ApplicationInstance.delete("test-app")
  7. Create if not exists (should handle 409 CONFLICT gracefully):

    app = await ApplicationInstance.create_if_not_exists({"name": "test-app", "region": "us-pdx-1"})
  8. Repeat key operations with SyncApplicationInstance to verify the synchronous variant works identically.

  9. Verify region warning: Call create() without a region field and confirm a FutureWarning is emitted about the missing region.

What to verify (expected behavior)

  • All CRUD operations (create, get, list, update, delete) work for both async and sync variants
  • create_if_not_exists returns an existing application without error if one with the same name already exists
  • The update method performs a merge (only overrides fields that are explicitly provided, preserves others)
  • ApplicationAPIError is raised with proper status_code and code fields on API errors
  • Configuration accepts all three input forms: dict, ApplicationCreateConfiguration, and raw Application model
  • Auto-generated names follow the app-{8-char-hex} pattern when no name is provided
  • Existing list operations (agents, drives, functions, volumes, etc.) still work correctly with the new pagination parameters
  • from blaxel.core import ApplicationInstance resolves without import errors
  • No regressions in existing DriveInstance/VolumeInstance behavior (they share the same descriptor pattern)
  • No tests were added — consider whether unit tests covering the CRUD helpers and descriptor logic should be included before merge

Note

Posted by PR Testing Guide · Tag @mendral-app with feedback.

The regenerated list endpoints now expect paginated {data, meta} wrappers,
but the API still returns bare arrays for older API versions. Fix:
- from_dict() in all *List models: if src_dict is a list, wrap as {"data": src_dict}
- list() methods in DriveInstance, VolumeInstance, SandboxInstance: extract .data from response
- list_executions in BlJobWrapper: extract .data from paginated response

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
mendral-app[bot]

This comment was marked as outdated.

Check for Error responses in _delete_application_by_name and
_delete_application_by_name_sync, raising ApplicationAPIError
instead of silently returning Error typed as Application.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

@mendral-app mendral-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Both previously flagged issues (missing error handling in delete helpers) are fixed in 527007f. The implementation correctly follows the existing resource patterns with proper error checking on all API calls.

Tag @mendral-app with feedback or questions. View session

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.

1 participant