Skip to content

Commit

Permalink
API doc, upgrade to Basilisp 0.3.2, -3.8 +3.13 (#2)
Browse files Browse the repository at this point in the history
Co-authored-by: ikappaki <ikappaki@users.noreply.github.com>
  • Loading branch information
ikappaki and ikappaki authored Nov 14, 2024
1 parent e560cc1 commit 0250866
Show file tree
Hide file tree
Showing 11 changed files with 368 additions and 239 deletions.
11 changes: 4 additions & 7 deletions .github/workflows/tests-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
version: ['3.8', '3.9', '3.10', '3.11', '3.12']
version: ['3.9', '3.10', '3.11', '3.12', '3.13']
include:
- os: windows-latest
version: '3.12'
version: '3.13'
- os: macos-latest
version: '3.12'
version: '3.13'

steps:
- uses: actions/checkout@v4
Expand All @@ -46,8 +46,5 @@ jobs:
run: poetry install

- name: Run tests
# workaround for https://github.com/basilisp-lang/basilisp/issues/1119
env:
PYTHONPATH: ${{ github.workspace }}
run: poetry run pytest -v
run: poetry run basilisp test -v

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
__pycache__/
*.py[cod]
*$py.class
.clj-kondo/
.lsp/

dist/

Expand Down
144 changes: 144 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Table of contents
- [`basilisp-nrepl-async.nrepl-server`](#basilisp-nrepl-async.nrepl-server)
- [`logger`](#basilisp-nrepl-async.nrepl-server/logger) - The logger for this namespace.
- [`ops`](#basilisp-nrepl-async.nrepl-server/ops) - A map of operations supported by the nREPL server (as keywords) to function handlers for those operations.
- [`server-start!`](#basilisp-nrepl-async.nrepl-server/server-start!) - Creates an <code>socketserver/ThreadingTCPServer</code> nREPL server with optional <code>OPTS</code>.
- [`basilisp-nrepl-async.utils`](#basilisp-nrepl-async.utils)
- [`error->str`](#basilisp-nrepl-async.utils/error->str) - Converts the <code>ERROR</code> list to a human-readable string and returns it.
- [`error-add`](#basilisp-nrepl-async.utils/error-add) - Adds additional <code>DETAILS</code> to the existing <code>ERROR</code> list and returns it.
- [`error-make`](#basilisp-nrepl-async.utils/error-make) - Returns a list from the provided error <code>DETAILS</code> to represent an error.
- [`with-eprotect`](#basilisp-nrepl-async.utils/with-eprotect) - Creates a try/catch wrapper around <code>BODY</code> that returns any exception as an <code>error</code> prefixed with the <code>id</code> from <code>ID-OR-OPTS</code>.

-----
# <a name="basilisp-nrepl-async.nrepl-server">basilisp-nrepl-async.nrepl-server</a>






## <a name="basilisp-nrepl-async.nrepl-server/logger">`logger`</a><a name="basilisp-nrepl-async.nrepl-server/logger"></a>




The logger for this namespace.
<p><sub><a href="/blob/main/src/basilisp_nrepl_async/nrepl_server.lpy#L19-L21">Source</a></sub></p>

## <a name="basilisp-nrepl-async.nrepl-server/ops">`ops`</a><a name="basilisp-nrepl-async.nrepl-server/ops"></a>




A map of operations supported by the nREPL server (as keywords) to function
handlers for those operations.
<p><sub><a href="/blob/main/src/basilisp_nrepl_async/nrepl_server.lpy#L332-L345">Source</a></sub></p>

## <a name="basilisp-nrepl-async.nrepl-server/server-start!">`server-start!`</a><a name="basilisp-nrepl-async.nrepl-server/server-start!"></a>
``` clojure

(server-start!)
(server-start! opts)
```
Function.

Creates an `socketserver/ThreadingTCPServer` nREPL server with
optional `OPTS`. By default, it listens on `127.0.0.1` at a random
available port and blocks for serving clients.

It prints out the [`nrepl-server-signature`](#basilisp-nrepl-async.nrepl-server/nrepl-server-signature) message at startup for
IDEs to pickup the host number to connect to.

`OPTS` is a map of options with the following optional keys

`:async?` If truthy, runs the server non-blocking in a separate
thread where requests are queued instead of executed
immediately. Returns a map with

:error Contains details of any error during server creation.

:host The host interface the server is bound to.

:port The local port the server is listening on.

:shutdown-fn The function to shutdown the server.

:work-fn The function to process any pending client requests.

`:host` The host address to bind to, defaults to 127.0.0.1.

`:nrepl-port-file` An optional filepath to write the port number
to. Typically set to .nrepl-port (the default) for the editors to
detect.

`:port` The port number to listen to, defaults to 0 which means to

`:server*` An optional promise delivering a map on server upbringing
with the following keys

:host The host interface the server is bound to.

:port The local port the server is listening on.

:shutdown-fn The function to shutdown the server.
<p><sub><a href="/blob/main/src/basilisp_nrepl_async/nrepl_server.lpy#L568-L659">Source</a></sub></p>

-----
# <a name="basilisp-nrepl-async.utils">basilisp-nrepl-async.utils</a>






## <a name="basilisp-nrepl-async.utils/error->str">`error->str`</a><a name="basilisp-nrepl-async.utils/error->str"></a>
``` clojure

(error->str error)
```
Macro.

Converts the `ERROR` list to a human-readable string and returns
it. Includes stack traces for any embedded exceptions.
<p><sub><a href="/blob/main/src/basilisp_nrepl_async/utils.lpy#L19-L29">Source</a></sub></p>

## <a name="basilisp-nrepl-async.utils/error-add">`error-add`</a><a name="basilisp-nrepl-async.utils/error-add"></a>
``` clojure

(error-add error & details)
```
Macro.

Adds additional `DETAILS` to the existing `ERROR` list and returns
it.
<p><sub><a href="/blob/main/src/basilisp_nrepl_async/utils.lpy#L12-L17">Source</a></sub></p>

## <a name="basilisp-nrepl-async.utils/error-make">`error-make`</a><a name="basilisp-nrepl-async.utils/error-make"></a>
``` clojure

(error-make & details)
```
Macro.

Returns a list from the provided error `DETAILS` to represent an
error.
<p><sub><a href="/blob/main/src/basilisp_nrepl_async/utils.lpy#L6-L10">Source</a></sub></p>

## <a name="basilisp-nrepl-async.utils/with-eprotect">`with-eprotect`</a><a name="basilisp-nrepl-async.utils/with-eprotect"></a>
``` clojure

(with-eprotect id-or-opts & body)
```
Macro.

Creates a try/catch wrapper around `BODY` that returns any exception
as an `error` prefixed with the `id` from `ID-OR-OPTS`.

`ID-OR-OPTS` is either a printable object used as the `id`, or a map
with the following keys

`:id` The `id` to prefix the error with.

`:on-err-str` [opt] A function to call if an exception is caught,
which accepts the formated error message as only argument.
<p><sub><a href="/blob/main/src/basilisp_nrepl_async/utils.lpy#L31-L61">Source</a></sub></p>
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## Unreleased

## 0.1.0b3

- Introduced API.md generated with quickdoc.
- Relaxed Basilisp version requirements to cover versions up to 1.0.0.
- Upgraded min Basilisp version to 0.3.2.
- Dropped 3.8 support and added 3.13 CI testing.

## 0.1.0b2

- Upgraded Basilisp to 0.2.4.
Expand Down
63 changes: 23 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
[![PyPI](https://img.shields.io/pypi/v/basilisp-nrepl-async.svg?style=flat-square)](https://pypi.org/project/basilisp-nrepl-async/) [![CI](https://github.com/ikappaki/basilisp-nrepl-async/actions/workflows/tests-run.yml/badge.svg)](https://github.com/ikappaki/basilisp-nrepl-async/actions/workflows/tests-run.yml)

# A Basilisp async nREPL server for cooperative multitasking
# Basilisp nREPL Server with Support for Cooperative Multitasking

[Basilisp](https://github.com/basilisp-lang/basilisp) is a Python-based Lisp implementation that offers broad compatibility with Clojure. Refer to [documentation](https://basilisp.readthedocs.io/en/latest/index.html) for more details.

## Overview
`basilisp-nrepl-async` is an nREPL server implementation for [Basilisp](https://basilisp.readthedocs.io/en/latest/) that allows client requests to be evaluated asynchronously by the main program at a chosen time and place. This enables cooperative multitasking on a single thread between the program and the nREPL server on the Python VM, while avoiding conflicts with the Global Interpreter Lock (GIL) and single threaded libraries.

This package provides an nREPL server implementation for Basilisp, evolved from the `basilisp.contrib.nrepl-server` namespace in Basilisp, addressing issues that arise from serving nREPL request in parallel with the main event loop.

Serving an nREPL client connection on a parallel thread in Python may conflict with the Global Interpreter Lock (GIL) and single-threaded libraries, potentially causing errors or crashes.

To mitigate this, the library includes an asynchronous mode where client requests are queued, allowing the main application to process them at a designated time within its main event loop. This is in addition to the synchronous multi-threaded mode, where client requests are handled immediately upon arrival.

## Installation

To install `basilisp-nrepl-async`, run:

```shell
pip install https://github.com/ikappaki/basilisp-nrepl-async/releases/download/v0.1.0b2/basilisp_nrepl_async-0.1.0b2-py3-none-any.whl
pip install https://github.com/ikappaki/basilisp-nrepl-async/releases/download/v0.1.0b3/basilisp_nrepl_async-0.1.0b3-py3-none-any.whl
```

## Usage

To start the nREPL server on a random port bound to the local interface, call the the `basilisp-nrepl-async.nrepl-server/server-start!` function with the `async?` option set to `true`, and periodically invoke the returned `work-fn` within your program's main loop.
See [API.md](API.md).

### Asynchronous mode

To start the nREPL server on a random port bound to the local interface in asynchronous mode, call [server-start!](API.md#basilisp-nrepl-async.nrepl-server/server-start!) with the `async?` option set to `true`. Periodically invoke the returned `work-fn` within your program's main loop to handle client requests.

```clojure
(require '[basilisp-nrepl-async.nrepl-server :as nr])
(import time)

;; Start the nREPL server on a separate thread to handle client
;; requests asynchronously
(def server-async (nr/start-server! {:async? true}))
(def server-async (nr/server-start! {:async? true}))
; nREPL server started on port 55144 on host 127.0.0.1 - nrepl://127.0.0.1:55144

;; Process client requests on this thread
Expand All @@ -40,45 +51,17 @@ To start the nREPL server on a random port bound to the local interface, call th

The server will create an `.nrepl-port` file in the current working directory with the port number, which nREPL-enabled Clojure editors can use to connect.

You can also pass additional options to the `server-start!` function, such as `:host`, `:port` and `:nrepl-port-file`, to explicitly configure the server's listening interface, port, and the file where the port number is written (typically `<your-basilisp-lib>/.nrepl-port` for integration with your editor). See the function documentation for more details in [src/basilisp_nrepl_async/nrepl_server.lpy](src/basilisp_nrepl_async/nrepl_server.lpy)

```clojure
"Create an nREPL server with `server-make` (of which see) according to
``opts`` if given, and blocks for serving clients.
It prints out the `nrepl-server-signature` message at startup for
IDEs to pickup the host number to connect to.
``opts`` is a map of options with the following optional keys
You can also pass additional options to the [server-start!](API.md#basilisp-nrepl-async.nrepl-server/server-start!) function, such as `:host`, `:port` and `:nrepl-port-file`, to explicitly configure the server's listening interface, port, and the file where the port number is written (typically `<your-basilisp-lib>/.nrepl-port` for integration with your editor).

:async? If truthy, runs the server non-blocking in a separate thread
where requests are queued instead of executed immediately. Returns a
map with
### Synchronous mode

:error :error Contains details of any error during server
creation.
To start in synchronous mode, call [server-start!](API.md#basilisp-nrepl-async.nrepl-server/server-start!) with the optional `:host`, `:port` and `:nrepl-port-file` keys. The server will block and handle client requests as they arrive.

:host The host interface the server is bound to.
:port The local port the server is listening on.
:shutdown-fn The function to shutdown the server.
:work-fn The function to process any pending client
requests.
:nrepl-port-file An optional filepath to write the port number
to. Typically set to .nrepl-port for the editors to detect.
:server* An optional promise of a map delivered on success with
:host The host interface the server is bound to.
:port The local port the server is listening on.
:shutdown-fn The function to shutdown the server.
```clojure
(require '[basilisp-nrepl-async.nrepl-server :as nr])

also see `server-make` for additionally supported ``opts`` keys."
(def server-async (nr/server-start! {:port 9999}))
; nREPL server started on port 9999 on host 127.0.0.1 - nrepl://127.0.0.1:9999
```

## Development and Testing
Expand Down
6 changes: 6 additions & 0 deletions bb.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{ :tasks
{quickdoc {:doc "Invoke quickdoc"
:extra-deps {io.github.borkdude/quickdoc {:git/sha "7e41f33d98e2ef697dd9ecdff8e84b779141c7a6"}}
:task (exec 'quickdoc.api/quickdoc)
:exec-args {:source-paths ["src/basilisp_nrepl_async/nrepl_server.lpy"
"src/basilisp_nrepl_async/utils.lpy"]}}}}
Loading

0 comments on commit 0250866

Please sign in to comment.