Skip to main content

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_typeopen_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

  1. plugin_type is set correctly
  2. All permissions used in code are declared in plugin.json
  3. Every external URL in http.* calls is listed in network_endpoints
  4. No ERP record data passed to event()
  5. erpdesktop validate passes with no errors
  6. erpdesktop package produces a valid .zip
  7. erpdesktop 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

erpdesktop-1.0.5.tar.gz (39.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

erpdesktop-1.0.5-py3-none-any.whl (41.6 kB view details)

Uploaded Python 3

File details

Details for the file erpdesktop-1.0.5.tar.gz.

File metadata

  • Download URL: erpdesktop-1.0.5.tar.gz
  • Upload date:
  • Size: 39.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.20

File hashes

Hashes for erpdesktop-1.0.5.tar.gz
Algorithm Hash digest
SHA256 593ff1c6d7e45a3950e0482266a706492e0aa5da7e5a69e3fb255106794058b3
MD5 6c3c3623be2218159b87af5339b5cf06
BLAKE2b-256 5917b3caafdd4497c802876a49a75c6d958c5c770a1b23ced1750c3a8b9bba8f

See more details on using hashes here.

File details

Details for the file erpdesktop-1.0.5-py3-none-any.whl.

File metadata

  • Download URL: erpdesktop-1.0.5-py3-none-any.whl
  • Upload date:
  • Size: 41.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.20

File hashes

Hashes for erpdesktop-1.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 22ea8740d689308f5e91afd69966be1a40877f0b3c6c6cc36882cc9957a9426f
MD5 8794554d0c6d85fde60c423ba454c232
BLAKE2b-256 82c66e96d4827aca43d968f49bb3e7ae14cada9adcff63a8a3c12d41a2e40325

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page