Python library for creating Disguise Designer plugins with DNS-SD discovery and remote Python execution
Project description
designer-plugin
A Python library for creating and managing plugins for Disguise Designer. This library provides:
- DNS-SD service publishing for plugin discovery
- Remote Python execution on Designer instances
- Multiple execution patterns (Client API, Functional API)
Installation
To install the plugin, use pip:
pip install designer-plugin
Publish Plugin
The DesignerPlugin class allows you to publish a plugin for the Disguise Designer application. The port parameter corresponds to an HTTP server that serves the plugin's web user interface. Below is an example of how to use it (without a server, for clarity).
In the working directory for the plugin (usually next to the plugin script) a d3plugin.json should be created. See The developer documentation for more information
{
"name": "MyPlugin",
"requiresSession": true
}
The script may work with asyncio or be synchronous - both options are shown in this example:
from designer_plugin import DesignerPlugin
# Synchronous usage
from time import sleep
with DesignerPlugin.default_init(12345) as plugin:
print("Plugin is published. Press Ctrl+C to stop.")
try:
while True:
sleep(3600)
except KeyboardInterrupt:
pass
# Asynchronous usage
import asyncio
async def main():
async with DesignerPlugin.default_init(port=12345) as plugin:
print("Plugin is published. Press Ctrl+C to stop.")
try:
await asyncio.Event().wait()
except asyncio.CancelledError:
pass
asyncio.run(main())
Publish options
If you would prefer not to use the d3plugin.json file, construct the DesignerPlugin object directly. The plugin's name and port number are required parameters. Optionally, the plugin can specify hostname, which can be used to direct Designer to a specific hostname when opening the plugin's web UI, and other metadata parameters are available, also.
Execute Python
Python scripts can be executed remotely on Designer via the plugin system.
Direct interaction with the plugin API endpoint requires extensive boilerplate code and JSON parsing. However, the Client API and Functional API simplify this process by providing an RPC (Remote Procedure Call) interface that abstracts away the underlying HTTP communication and payload management.
Important: The Designer plugin API only supports Python 2.7, not Python 3. Both the Client API and Functional API attempt to automatically convert your Python 3 code to Python 2.7 (f-strings and type hints are supported). However, some Python 3 features may not be fully compatible and conversion may fail in certain cases.
Stub file
To enable IDE autocomplete and type checking for Designer's Python API, install the stub file package:
pip install designer-plugin-pystub
Once installed, import the stubs.
Important:
pystubprovides type hints for Designer's API objects but not their implementations. These objects only exist in Designer's runtime and cannot be used in local Python code. They must only be referenced in code that will be executed remotely in Designer.
from designer_plugin.pystub import *
This allows you to get autocomplete for Designer objects like resourceManager, Screen2, Path, etc., while writing your plugin code.
Client API
The Client API allows you to define a class with methods that execute remotely on Designer by simply inheriting from D3PluginClient. The Client API supports both async and sync methods.
Example
from designer_plugin.d3sdk import D3PluginClient
from designer_plugin.pystub import *
# 1. Sync example -----------------------------------
class MySyncPlugin(D3PluginClient):
def get_uid(self, surface_name: str) -> str:
surface: Screen2 = resourceManager.load(
Path(f'objects/screen2/{surface_name}.apx'),
Screen2)
return str(surface.uid)
my_sync_plugin = MySyncPlugin()
with my_sync_plugin.session('localhost', 80):
uid = my_sync_plugin.get_uid("surface 1")
# 2. Async example ----------------------------------
# Define your class by inheriting from D3PluginClient
class MyAsyncPlugin(D3PluginClient):
async def my_time(self) -> str:
# Builtin imports must be done within methods for remote execution
import datetime
return str(datetime.datetime.now())
async def get_surface_uid_with_time(
self,
surface_name: str
) -> dict[str, str]:
surface: Screen2 = resourceManager.load(
Path(f'objects/screen2/{surface_name}.apx'),
Screen2)
return {
"name": surface.description,
"uid": str(surface.uid),
"time": await self.my_time() # Supports method chaining
}
# Usage
async def main():
# Instantiate your plugin
my_async_plugin = MyAsyncPlugin()
# Start async session with Designer
async with my_async_plugin.async_session('localhost', 80):
# Methods execute remotely on Designer and return values
surface_info = await my_async_plugin.get_surface_uid_with_time("surface 1")
print(surface_info)
import asyncio
asyncio.run(main())
Functional API
The Functional API provides finer control over remote execution compared to the Client API. While the Client API automatically manages the entire execution lifecycle (registration and execution are transparent), the Functional API gives you explicit control over:
- Payload generation: Decorators add a
payload()method to generate execution payloads - Session management: You manually create sessions and control when to register modules
- Function grouping: Group related functions into modules for efficient reuse
- Response handling: Choose between
session.execute()for full response (status, logs, return value) orsession.rpc()for just the return value
The Functional API offers two decorators: @d3pythonscript and @d3function:
@d3pythonscript:- Does not require registration.
- Best for simple scripts executed once or infrequently.
@d3function:- Must be registered on Designer before execution.
- Functions decorated with the same
module_nameare grouped together and can call each other, enabling function chaining and code reuse. - Registration happens automatically on the first call to
execute()orrpc()that references the module — no need to declare modules upfront. You can also pre-register specific modules by passing them to the session context manager (e.g.,D3AsyncSession('localhost', 80, {"mymodule"})).
Jupyter Notebook: File-level imports (e.g.,
import numpy as npin a separate cell) cannot be automatically detected. In Jupyter, place any required imports inside the function body itself:@d3function("mymodule") def my_fn(): import numpy as np return np.array([1, 2])
Session API Methods
Both D3AsyncSession and D3Session provide two methods for executing functions:
session.rpc(payload)- Returns only the return value from the function execution. Simpler for most use cases.session.execute(payload)- Returns aPluginResponseobject containing:returnValue: The function's return valuestatus: Execution status (code, message, details)d3Log: Designer console output during executionpythonLog: Python-specific output (print statements, warnings)
Example
from designer_plugin.d3sdk import d3pythonscript, d3function, D3AsyncSession
from designer_plugin.pystub import *
# 1. @d3pythonscript - simple one-off execution
@d3pythonscript
def rename_surface(surface_name: str, new_name: str):
surface: Screen2 = resourceManager.load(
Path(f'objects/screen2/{surface_name}.apx'),
Screen2)
surface.rename(surface.path.replaceFilename(new_name))
# 2. @d3function - reusable module-based functions
@d3function("mymodule")
def rename_surface_get_time(surface_name: str, new_name: str) -> str:
surface: Screen2 = resourceManager.load(
Path(f'objects/screen2/{surface_name}.apx'),
Screen2)
surface.rename(surface.path.replaceFilename(new_name))
return my_time() # Call other functions in the same module
@d3function("mymodule")
def my_time() -> str:
import datetime
return str(datetime.datetime.now())
# Usage with async session
async with D3AsyncSession('localhost', 80) as session:
# d3pythonscript: no registration needed
await session.rpc(rename_surface.payload("surface 1", "surface 2"))
# d3function: module is registered automatically on first call
time: str = await session.rpc(
rename_surface_get_time.payload("surface 1", "surface 2"))
# Use execute() for full response with logs and status
from designer_plugin import PluginResponse
response: PluginResponse = await session.execute(
rename_surface_get_time.payload("surface 1", "surface 2"))
print(f"Status: {response.status.code}")
print(f"Return value: {response.returnValue}")
# Sync usage
from designer_plugin.d3sdk import D3Session
with D3Session('localhost', 80) as session:
session.rpc(rename_surface.payload("surface 1", "surface 2"))
Logging
By default, designer_plugin logging is disabled. To enable it:
import logging
logging.basicConfig(level=logging.INFO)
logging.getLogger('designer_plugin').setLevel(logging.DEBUG)
License
This project is licensed under the MIT License. See the LICENSE file for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file designer_plugin-1.3.1.tar.gz.
File metadata
- Download URL: designer_plugin-1.3.1.tar.gz
- Upload date:
- Size: 51.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5517fb329b5d41ab5040a1d5c4a73f91518dde26f4f3f02371b78005ff118fd
|
|
| MD5 |
174b1267590de62be46c2eda5d808540
|
|
| BLAKE2b-256 |
3d1106eda586e9e89b8bc0f0f75dde69c505b24492dd46dbef69e864ad64cf1b
|
Provenance
The following attestation bundles were made for designer_plugin-1.3.1.tar.gz:
Publisher:
release.yml on disguise-one/python-plugin
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
designer_plugin-1.3.1.tar.gz -
Subject digest:
d5517fb329b5d41ab5040a1d5c4a73f91518dde26f4f3f02371b78005ff118fd - Sigstore transparency entry: 1409268055
- Sigstore integration time:
-
Permalink:
disguise-one/python-plugin@092b2c9d97901912491e4244054322cc8ecac905 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/disguise-one
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@092b2c9d97901912491e4244054322cc8ecac905 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file designer_plugin-1.3.1-py3-none-any.whl.
File metadata
- Download URL: designer_plugin-1.3.1-py3-none-any.whl
- Upload date:
- Size: 34.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98c0a5b41a8d47f4e79d159653c0756865acd6e7f6887143465733b3f7ee9b42
|
|
| MD5 |
c7e546018f5c1c784ee3d6a342bcd819
|
|
| BLAKE2b-256 |
b9fa4a06e5ce8cc175600b9acbe19707f4ca1a00dcb7e4d01486608271628583
|
Provenance
The following attestation bundles were made for designer_plugin-1.3.1-py3-none-any.whl:
Publisher:
release.yml on disguise-one/python-plugin
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
designer_plugin-1.3.1-py3-none-any.whl -
Subject digest:
98c0a5b41a8d47f4e79d159653c0756865acd6e7f6887143465733b3f7ee9b42 - Sigstore transparency entry: 1409268059
- Sigstore integration time:
-
Permalink:
disguise-one/python-plugin@092b2c9d97901912491e4244054322cc8ecac905 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/disguise-one
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@092b2c9d97901912491e4244054322cc8ecac905 -
Trigger Event:
workflow_dispatch
-
Statement type: