{"id":14128640,"url":"https://github.com/jcaw/porthole-python-client","last_synced_at":"2026-02-10T08:33:15.651Z","repository":{"id":57426113,"uuid":"190606072","full_name":"jcaw/porthole-python-client","owner":"jcaw","description":"🔭 Python client for Emacs Porthole. An easy way to make RPC calls to Emacs.","archived":false,"fork":false,"pushed_at":"2020-01-21T16:59:59.000Z","size":129,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-24T09:21:22.635Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jcaw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-06-06T15:33:56.000Z","updated_at":"2022-02-05T18:01:40.000Z","dependencies_parsed_at":"2022-09-11T04:20:27.790Z","dependency_job_id":null,"html_url":"https://github.com/jcaw/porthole-python-client","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcaw%2Fporthole-python-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcaw%2Fporthole-python-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcaw%2Fporthole-python-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcaw%2Fporthole-python-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jcaw","download_url":"https://codeload.github.com/jcaw/porthole-python-client/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248186118,"owners_count":21061609,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-15T16:01:58.768Z","updated_at":"2026-02-10T08:33:14.359Z","avatar_url":"https://github.com/jcaw.png","language":"Python","readme":"\u003cp align=center\u003e\n    \u003cimg src=\"media/logo.png\" alt=\"Porthole Python logo\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=center\u003ePorthole Python Client\u003c/h1\u003e\n\n\u003cp align=center\u003ePython client for Emacs RPC servers started with \u003ca href=\"https://github.com/jcaw/porthole\"\u003ePorthole\u003c/a\u003e.\u003c/p\u003e\n\n\u003cp align=center\u003e\n :sailboat:\n\u003c/p\u003e\n\n---\n\n\u003c!-- ## What is this Package? --\u003e\n\n[Porthole](https://github.com/jcaw/porthole) lets you start RPC servers in\nEmacs. These servers allow Elisp to be invoked remotely via HTTP requests.\n\nThis is a client written in Python that makes it easier to communicate with\nPorthole. Here's how you'd make a simple call (let's ignore error handling, for\nnow):\n\n```python\nresult = emacs_porthole.call(server=\"my-server\", method=\"insert\", params=[\"Some text to insert.\"])\n```\n\nThe text will be inserted into Emacs, and the result of the operation returned.\nSee [Basic Usage](#basic-usage) for more.\n\n---\n\n## Installation\n\nInstall it from PyPI.\n\n`pip install -U emacs_porthole`\n\n## Basic Usage\n\nThis client is designed to make it very easy to build RPC tools on top of Emacs. You can write Python scripts to interface with your personal Emacs instance, or build a larger package on the framework.\n\nLet's start a server in Emacs with [Porthole](https://github.com/jcaw/porthole):\n\n```emacs-lisp\n;; All you need is a name. Everything else is automatic.\n(porthole-start-server \"pyrate-server\")\n;; You need to tell the server which functions are allowed to be called remotely. We'll expose the `insert` function.\n(porthole-expose-function \"pyrate-server\" 'insert)\n```\n\nYou can now send messages with the client.\n\n```python\nfrom emacs_porthole import call\n\nresult = call(\"pyrate-server\", method=\"insert\", params=[\"Some text to insert\"])\n```\n\n## Error Handling\n\nNetworking isn't foolproof. `emacs_porthole` raises errors to signal problems. Here's a complete example, with error handling:\n\n```python\nfrom emacs_porthole import (\n    call,\n    PortholeConnectionError,\n    MethodNotExposedError,\n    InternalMethodError,\n    TimeoutError\n)\n\ndef sum_in_emacs():\n    try:\n        return call(\"pyrate-server\", method=\"+\", params=[1, 2, 3])\n    except TimeoutError:\n        print(\"The request timed out. Is Emacs busy?\")\n    except PortholeConnectionError:\n        print(\"There was a problem connecting to the server. Is it running?\")\n    except MethodNotExposedError:\n        print(\"The method wasn't exposed! Remember to expose it in the porthole server.\")\n    except InternalMethodError as e:\n        # This will be raised when the function was executed, and raised an error during execution.\n        print(\"There was an error executing the method. Details:\\n\"\n              \"error_type: {}\\n\".format(e.elisp_error_symbol)\n              \"error_data: {}\".format(e.elisp_error_data))\n```\n\nErrors are split into connection errors, and problems with the RPC request.\nThese are the main errors you may want to catch:\n\n### Connection Errors\n\nThese errors are raised when `emacs_porthole` can't connect.\n\n- `ServerNotRunningError` - This error means the server isn't running.\n\n  This error will be raised if there's an error connecting to the Porthole\n  server *other* than a timeout. If the server is dead, or an HTTP response\n  other that `200: Success` is returned, this will be raised.\n\n- `TimeoutError` - This error will be raised when the request times out. This\n  *normally* means the server is running, but the Emacs session is busy.\n\n- `PortholeConnectionError` - This is the superclass for both\n  `ServerNotRunningError` and `TimeoutError`. You can use this error to catch\n  all connection issues.\n\nMore specific errors exist, which are subclasses of these errors.\n\n### RPC-Related Errors\n\nThese errors are raised when a connection was made, but there was a problem with\nthe underlying RPC call. There are 5 of these errors, but you should only\nencounter two:\n\n- `InternalMethodError` - This will be raised if Emacs tried to execute your\n  function, but it raised an error. This is the RPC error you're most likely to\n  encounter. The underlying Elisp error will be attached to this exception in\n  the following members:\n  - `.elisp_error_type` - This holds the type of error that was raised. It will\n    be a string, representing the error's symbol.\n  - `.elisp_error_data` - Additional data about the error. Elisp errors are\n    `cons` cells, composed of an error symbol (the type) and a `cdr` list\n    containing the data. This member will be the data list, translated into\n    Python.\n\n    Note that it's possible for Emacs to raise an error that can't be encoded\n    into JSON. In that case, some data will be replaced by a string placeholder,\n    explaining the problem. In practice, you are unlikely to encounter this.\n\n- `MethodNotExposedError` - This will be raised if you try to call a function\n  that isn't exposed, or doesn't exist. It means the Porthole server has not\n  allowed execution of your desired function.\n\nIf you see one of the other `json_rpc` errors, something went wrong. Please [open an\nissue](http://github.com/jcaw/porthole-python-client/issues).\n\nNote that these errors are only raised by the `call` method. See the next\nsection for details.\n\n## Raw JSON-RPC Objects\n\nIf you like, you can circumvent the the `call` method and get the raw JSON-RPC\n2.0 response directly. Use the `call_raw` method.\n\n```python\njson_response_dict = call_raw(server_name, method, params)\n```\n\nThis will return a JSON-RPC 2.0 response dictionary when successful. The dictionary will have the following structure:\n\n```python\n# For a successful result\n{\n    \"jsonrpc\": \"2.0\",\n    \"result\": 6,\n    \"id\": ...\n}\n\n# For an error\n{\n    \"jsonrpc\": \"2.0\",\n    \"error\": {\n         \"code\": -32603,\n         \"message\": \"There was an error calling the method.\",\n         # The \"data\" member will be None, unless there is data, then it will be a dict.\n         \"data\": {\n             # There is not always an underlying error. This member will only exist when there is.\n             \"underlying-error\": {\n                 \"type\": \"wrong-type-argument\",\n                 \"data\": [\"stringp\" 1],\n             }\n         }\n    },\n    \"id\": ...\n}\n```\n\nWith error handling, then:\n\n```python\nimport emacs_porthole\n\ndef sum_in_emacs_raw():\n   # You still need to catch connection errors\n   try:\n        return call_raw(\"pyrate-server\", method=\"+\", [1, 2, 3])\n    except TimeoutError:\n        print(\"The request timed out. Is Emacs busy?\")\n    except PortholeConnectionError:\n        print(\"There was a problem connecting to the server. Is it running?\")\n\n```\n\nYou'll need to parse the JSON-RPC response object yourself.\n\n\n## TODO\n\n- Batch Requests\n","funding_links":[],"categories":["Python"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcaw%2Fporthole-python-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjcaw%2Fporthole-python-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcaw%2Fporthole-python-client/lists"}