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.3.tar.gz (38.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.3-py3-none-any.whl (40.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: erpdesktop-1.0.3.tar.gz
  • Upload date:
  • Size: 38.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.3.tar.gz
Algorithm Hash digest
SHA256 27a0d29ff6768ac1034f390df3f9707f66e8d575b308245c48043780c8012e4d
MD5 bbe428f0a6aec9b02a07bbb58ce64605
BLAKE2b-256 f5ec5495006f3c712ebe0aecba4f641d4b11dd1e3da62345fa968a9d54ea8e54

See more details on using hashes here.

File details

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

File metadata

  • Download URL: erpdesktop-1.0.3-py3-none-any.whl
  • Upload date:
  • Size: 40.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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 90c018ff91eea61ce1219dbb5127832e884a865607052efd28d87134dbd1c3b7
MD5 b73ae9facc9828b56b77d32da5a5edd2
BLAKE2b-256 4f9788cb9e7a76e2e851d6a9964f20faaff1c7bc2b676b0d459516878116638f

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