ERPDesktop plugin SDK — build, package, and publish plugins for the ERPDesktop marketplace
Project description
ERPDesktop Plugin SDK
Build, package, and publish plugins for the ERPDesktop marketplace. ERPDesktop plugins run as Python sidecars alongside the desktop app and communicate with ERPNext/Odoo over a secure JSON-RPC channel.
Install
pip install erpdesktop
Requires Python 3.11+.
Quick start
# Scaffold a new plugin
erpdesktop init com.yourcompany.myplugin
# Validate plugin.json
erpdesktop validate
# Package into a distributable .zip
erpdesktop package
# Publish to the marketplace
erpdesktop publish --changelog "Initial release"
Plugin lifecycle
from erpdesktop import PluginBase, log, event
class MyPlugin(PluginBase):
plugin_id = "com.example.myplugin"
plugin_version = "1.0.0"
def on_start(self, config: dict) -> None:
"""Called when the plugin is started. Register your commands here."""
log("info", "Plugin started", branch=config.get("branch"))
self.register_command("ping", self.handle_ping)
def on_stop(self) -> None:
"""Called on graceful shutdown."""
log("info", "Plugin stopping")
def on_config_change(self, config: dict) -> None:
"""Called when the user updates plugin settings."""
pass
def handle_ping(self, params: dict) -> dict:
return {"pong": True}
if __name__ == "__main__":
MyPlugin().run()
Modules
log — structured logging
from erpdesktop import log
log("info", "Sync complete", records=47, duration_ms=320)
log("error", "Device unreachable", device_id="K50-001")
# levels: debug | info | warn | error | critical
event — telemetry
Emit custom analytics events visible in the Publisher Dashboard. Do not include personal data or ERP record contents.
from erpdesktop import event
event("sync_completed", records=47, duration_ms=320)
event("device_connected", model="K50", firmware="2.1.3")
erpnext_request / odoo_request — ERP API
Call your connected ERPNext or Odoo site. Requires network.outbound.erpnext permission.
from erpdesktop import erpnext_request, odoo_request
# ERPNext
result = erpnext_request("GET", "/api/resource/Employee", params={"limit": 10})
# Odoo
result = odoo_request("POST", "/web/dataset/call_kw", json={...})
http — external HTTP
Make calls to third-party APIs. Requires network.outbound.internet permission and
the endpoint declared in network_endpoints in your plugin.json.
from erpdesktop import http
resp = http.get("https://api.example.com/data", params={"key": "val"})
resp = http.post("https://api.example.com/sync", json={"records": [...]})
storage — persistent key-value store
from erpdesktop import storage
storage.set("last_sync", "2024-01-15T10:30:00")
value = storage.get("last_sync") # returns str or None
value = storage.get("last_sync", "never") # with fallback
secrets — encrypted credential storage
Store API keys and passwords in the OS keychain — never in config files.
from erpdesktop import secrets
secrets.set("api_key", "sk-abc123")
key = secrets.get("api_key") # returns str or None
key = secrets.get("api_key", "") # with fallback
permissions — runtime permission checks
from erpdesktop import permissions
# Check before doing something sensitive
if permissions.require("network.outbound.internet"):
result = http.get("https://api.example.com/data")
# Raise PermissionDenied automatically if not granted
permissions.enforce("network.outbound.erpnext")
# Check multiple
if permissions.has_all("network.outbound.erpnext", "storage.read"):
...
if permissions.has_any("network.outbound.erpnext", "network.outbound.internet"):
...
device — biometric/access device leasing
Claim exclusive access to a physical device (e.g. fingerprint reader) so two plugins don't conflict.
from erpdesktop import device
if device.lease("ZK-192.168.1.10", metadata={"model": "K50"}):
# we have exclusive access
...
device.release("ZK-192.168.1.10")
device.renew("ZK-192.168.1.10") # extend lease
owner = device.is_leased("ZK-192.168.1.10") # returns plugin_id or None
notify — desktop notifications
from erpdesktop import notify
notify.send("Sync complete", "47 attendance records pushed to ERPNext")
notify.send("Device offline", "ZK-K50 not reachable", urgent=True)
i18n — translations
from erpdesktop import i18n
i18n.set_locale("ne") # Nepali
label = i18n.t("sync.button", fallback="Sync")
label = i18n.t("greeting", fallback="Hello {name}", name="Ram")
Place translation files at locales/<lang>.json in your plugin directory:
{ "sync.button": "सिंक गर्नुहोस्", "greeting": "नमस्ते {name}" }
plugin.json reference
{
"id": "com.yourcompany.myplugin",
"name": "My Plugin",
"version": "1.0.0",
"description": "What your plugin does.",
"author": "Your Company",
"plugin_type": "commercial",
"entry": "main.py",
"permissions": [
"network.outbound.erpnext",
"network.outbound.internet",
"storage.read",
"storage.write"
],
"network_endpoints": [
{
"url": "https://api.example.com",
"purpose": "Sync attendance records",
"data_sent": "Employee IDs, timestamps"
}
],
"config_schema": {
"branch": { "type": "string", "label": "Branch", "required": true }
}
}
plugin_type — open_source (requires source_url), commercial, or private
network_endpoints — required when network.outbound.internet is declared.
Shown to the user at install time. The SDK enforces the allowlist at runtime.
CLI reference
| Command | Description |
|---|---|
erpdesktop login |
Authenticate with the ERPDesktop marketplace |
erpdesktop logout |
Sign out |
erpdesktop whoami |
Show current logged-in publisher |
erpdesktop init <id> |
Scaffold a new plugin |
erpdesktop validate |
Validate plugin.json against the schema |
erpdesktop package |
Build a .zip for distribution |
erpdesktop publish |
Publish to the marketplace |
erpdesktop logs |
Stream live plugin logs |
erpdesktop dev |
Start plugin in dev/watch mode |
erpdesktop --version |
Show SDK version |
Publishing checklist
plugin_typeis set correctly- All
permissionsused in code are declared inplugin.json - Every external URL in
http.*calls is listed innetwork_endpoints - No ERP record data passed to
event() erpdesktop validatepasses with no errorserpdesktop packageproduces a valid.ziperpdesktop publish --changelog "What changed"
License
MIT © BatchNepal
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 erpdesktop-1.0.1.tar.gz.
File metadata
- Download URL: erpdesktop-1.0.1.tar.gz
- Upload date:
- Size: 31.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
94f38a9fc7fc19bfe18cf564df21f35088a6c7eca536619a39a8aff3a8cf829a
|
|
| MD5 |
49a3a495369a7382e1808a15bc0abed7
|
|
| BLAKE2b-256 |
17a3f33854d3a5300f666fb6a5d5d25a56387f8ec544bbd4e0652030d8f8186a
|
File details
Details for the file erpdesktop-1.0.1-py3-none-any.whl.
File metadata
- Download URL: erpdesktop-1.0.1-py3-none-any.whl
- Upload date:
- Size: 32.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6012b31257f860ee48a6f882b4cb86a7a89de446437223b7718735bbd456f86e
|
|
| MD5 |
ca1949221b183d6375effef549ee934d
|
|
| BLAKE2b-256 |
e2367dca37448f4982a5a760cc79747bed8be1c3df6b3d92aa01754137099ec4
|