nrepl-python 0.3.8__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,126 @@
1
+ # nrepl-python - NREPL implementation in Python (See nrepl.org)
2
+ #
3
+ # SPDX-FileCopyrightText: 2024-2026 Nicolas Graves
4
+ #
5
+ # SPDX-License-Identifier: GPL-3.0-or-later
6
+ import logging
7
+ from .base import BaseMiddleware
8
+ from nrepl.transport import Message
9
+ import nrepl.dag as dag
10
+
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class MiddlewareManagementMiddleware(BaseMiddleware):
16
+ def __init__(self):
17
+ requires = set()
18
+ expects = set()
19
+ handles = {
20
+ "add-middleware": {
21
+ "doc": "Add new middleware to the stack.",
22
+ "requires": {"middleware": "A list of middleware to add."},
23
+ "optional": {"extra-namespaces": "A list of extra namespaces to load."},
24
+ "returns": {
25
+ "unresolved-middleware": "List of middleware that could not be resolved.",
26
+ },
27
+ },
28
+ "ls-middleware": {
29
+ "doc": "List the current middleware stack.",
30
+ "requires": {},
31
+ "optional": {},
32
+ "returns": {"middleware": "List of loaded middleware."},
33
+ },
34
+ "swap-middleware": {
35
+ "doc": "Replace the entire middleware stack.",
36
+ "requires": {"middleware": "A list of middleware."},
37
+ "optional": {"extra-namespaces": "A list of extra namespaces to load."},
38
+ "returns": {
39
+ "unresolved-middleware": "List of middleware that could not be resolved.",
40
+ },
41
+ },
42
+ }
43
+ super().__init__(requires, expects, handles)
44
+
45
+ def _resolve_middleware(self, request):
46
+ extra_namespaces = request.get("extra-namespaces") or []
47
+ all_namespaces = [globals()]
48
+ unresolved_namespaces = []
49
+
50
+ for ns in extra_namespaces:
51
+ try:
52
+ all_namespaces.append(__import__(ns, fromlist=[""]))
53
+ except ModuleNotFoundError as e:
54
+ logger.debug(f"{e}")
55
+ unresolved_namespaces.append(ns)
56
+
57
+ if unresolved_namespaces:
58
+ return [], [], unresolved_namespaces
59
+
60
+ resolved_middlewares = []
61
+ unresolved = []
62
+ for name in request.get("middleware"):
63
+ resolved = None
64
+ for namespace in [globals()] + all_namespaces:
65
+ try:
66
+ resolved = getattr(namespace, name, None)
67
+ if resolved:
68
+ resolved_middlewares.append(resolved())
69
+ break
70
+ except AttributeError:
71
+ continue
72
+ if not resolved:
73
+ unresolved.append(name)
74
+
75
+ return resolved_middlewares, unresolved, []
76
+
77
+ def add_middleware(self, request: Message, server, connection) -> Message:
78
+ resolved, unresolved, unresolved_ns = self._resolve_middleware(request)
79
+ if unresolved_ns:
80
+ return Message({
81
+ "status": ["error", "unresolved-namespace"],
82
+ "unresolved-namespace": unresolved_ns,
83
+ })
84
+ if unresolved:
85
+ return Message({
86
+ "status": ["error", "unresolved-middleware"],
87
+ "unresolved-middleware": unresolved,
88
+ })
89
+
90
+ new_nodes = []
91
+ for middleware in resolved:
92
+ if middleware in server.middleware_dag.nodes.keys():
93
+ return Message({
94
+ "status": ["error", f"middleware-{middleware}-already-in-DAG"]
95
+ })
96
+
97
+ new_node = dag.Node(middleware)
98
+ server.middleware_dag.nodes[middleware] = new_node
99
+ # server.middleware_dag.roots.append(new_node)
100
+ new_nodes.append(new_node)
101
+ server.middleware_dag._add_node(new_node)
102
+
103
+ server.middleware_dag._refresh_roots()
104
+ return Message({"status": ["done"]})
105
+
106
+ def ls_middleware(self, request: Message, server, connection) -> Message:
107
+ return Message({
108
+ "status": ["done"],
109
+ "middleware": server.middleware_dag.to_list(),
110
+ })
111
+
112
+ def swap_middleware(self, request: Message, server, connection) -> Message:
113
+ resolved, unresolved, unresolved_ns = self._resolve_middleware(request)
114
+ if unresolved_ns:
115
+ return Message({
116
+ "status": ["error", "unresolved-namespace"],
117
+ "unresolved-namespace": unresolved_ns,
118
+ })
119
+ if unresolved:
120
+ return Message({
121
+ "status": ["error", "unresolved-middleware"],
122
+ "unresolved-middleware": unresolved,
123
+ })
124
+
125
+ server.middleware_dag = dag.DirectAcyclicGraph(resolved)
126
+ return Message({"status": ["done"]})
@@ -0,0 +1,73 @@
1
+ # nrepl-python - NREPL implementation in Python (See nrepl.org)
2
+ #
3
+ # SPDX-FileCopyrightText: 2024-2026 Nicolas Graves
4
+ #
5
+ # SPDX-License-Identifier: GPL-3.0-or-later
6
+ from nrepl.session import Session
7
+ from nrepl.transport import Message
8
+ from .base import BaseMiddleware, _add_id
9
+
10
+
11
+ class SessionManagementMiddleware(BaseMiddleware):
12
+ def __init__(self):
13
+ requires = set()
14
+ expects = set()
15
+ handles = {
16
+ "clone": {
17
+ "doc": "\
18
+ Clones the current session, returning the ID of the newly-created session.",
19
+ "requires": {},
20
+ "optional": {"session"},
21
+ "returns": {"new-session": "The ID of the new session."},
22
+ },
23
+ "close": {
24
+ "doc": "Closes the specified session.",
25
+ "requires": {"session": "The ID of the session to be closed."},
26
+ "optional": {},
27
+ "returns": {},
28
+ },
29
+ "ls-sessions": {
30
+ "doc": "Lists the IDs of all active sessions.",
31
+ "requires": {},
32
+ "optional": {},
33
+ "returns": {"sessions": "A list of all available session IDs"},
34
+ },
35
+ }
36
+ super().__init__(requires, expects, handles)
37
+
38
+ def clone(self, request: Message, server, connection) -> Message:
39
+ if "session" in request.keys():
40
+ old_id = request.get("session")
41
+ if old_id in server.sessions.keys():
42
+ new = server.sessions[request.get("session")].clone()
43
+ response = Message(
44
+ {"status": ["done"], "new-session": new.session_id}
45
+ )
46
+ else:
47
+ response = Message({"status": ["error", "session-not-found"]})
48
+ else:
49
+ new = Session()
50
+ server.sessions[new.session_id] = new
51
+ response = Message(
52
+ {"status": ["done"], "new-session": new.session_id}
53
+ )
54
+
55
+ _add_id(response, request.get("id"))
56
+ return response
57
+
58
+ def close(self, request: Message, server, connection) -> Message:
59
+ if request.get("session") in server.sessions.keys():
60
+ del server.sessions[request.get("session")]
61
+ response = Message({"status": ["done"]})
62
+ _add_id(response, request.get("id"))
63
+ return response
64
+ else:
65
+ return Message({"status": ["error", "session-not-found"]})
66
+
67
+ def ls_sessions(self, request: Message, server, connection) -> Message:
68
+ response = Message({
69
+ "status": ["done"],
70
+ "id": request.get("id"),
71
+ "sessions": list(server.sessions.keys()),
72
+ })
73
+ return response
nrepl/server.py ADDED
@@ -0,0 +1,83 @@
1
+ # nrepl-python - NREPL implementation in Python (See nrepl.org)
2
+ #
3
+ # SPDX-FileCopyrightText: 2024-2026 Nicolas Graves
4
+ #
5
+ # SPDX-License-Identifier: GPL-3.0-or-later
6
+ import argparse
7
+ import gevent
8
+ from gevent import server
9
+ from nrepl.transport import Message, encode, decode
10
+ import logging
11
+ import nrepl.dag as dag
12
+ import nrepl.middleware as middleware
13
+ from nrepl.session import Session
14
+
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class NREPLServer:
20
+ def __init__(self, host="localhost", port=7888, middlewares=None) -> None:
21
+ self.server = server.StreamServer((host, port), self.handle_connection)
22
+ self.sessions: dict[str, Session] = {}
23
+ self.middleware_dag = dag.DirectAcyclicGraph(middlewares)
24
+
25
+ def start(self) -> None:
26
+ logger.info(f"Running NREPL Python on {self.server.address}")
27
+ self.server.serve_forever()
28
+
29
+ def handle_connection(self, connection, address) -> None:
30
+ logger.debug(f"Accepted connection from {address}")
31
+ while True:
32
+ try:
33
+ data = connection.recv(10000)
34
+ if not data:
35
+ break
36
+ gevent.spawn(self.handle_request, data, connection)
37
+ except Exception as e:
38
+ logger.info(f"Unexpected error: {e}")
39
+ break
40
+ logger.debug(f"Closing connection from {address}")
41
+ connection.close()
42
+
43
+ def handle_request(self, data, connection) -> None:
44
+ try:
45
+ request = decode(data)
46
+ except Exception:
47
+ self.send_response(Message({
48
+ "status": ["error", "failed-to-decode", data]
49
+ }), connection)
50
+ return
51
+ self.middleware_dag.execute(request, self, connection)
52
+
53
+ def send_response(self, response: Message, connection) -> None:
54
+ if not connection:
55
+ raise RuntimeError("No active connection to send responses")
56
+ try:
57
+ connection.sendall(encode(response))
58
+ except Exception as e:
59
+ raise RuntimeError(f"Error sending response {response}: {e}")
60
+
61
+
62
+ def main():
63
+ parser = argparse.ArgumentParser()
64
+ parser.add_argument("--debug", action="store_true")
65
+ parser.add_argument("--port", "-p", type=int, default=7888)
66
+ args = parser.parse_args()
67
+ middlewares = [
68
+ middleware.SessionManagementMiddleware(),
69
+ middleware.EvalMiddleware(),
70
+ middleware.MiddlewareManagementMiddleware(),
71
+ middleware.DescribeMiddleware(),
72
+ ]
73
+
74
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
75
+ if args.debug:
76
+ logging.getLogger("nrepl").setLevel(logging.DEBUG)
77
+
78
+ nrepl_server = NREPLServer(port=args.port, middlewares=middlewares)
79
+ nrepl_server.start()
80
+
81
+
82
+ if __name__ == "__main__":
83
+ main()
nrepl/session.py ADDED
@@ -0,0 +1,79 @@
1
+ # nrepl-python - NREPL implementation in Python (See nrepl.org)
2
+ #
3
+ # SPDX-FileCopyrightText: 2024-2026 Nicolas Graves
4
+ #
5
+ # SPDX-License-Identifier: GPL-3.0-or-later
6
+ import threading
7
+ import pickle
8
+ import uuid
9
+ from concurrent.futures import ThreadPoolExecutor
10
+ from nrepl.interactiveshell import NREPLEvalIORunner, NREPLShell
11
+ from typing import Any, Callable
12
+
13
+ session_lock = threading.Lock()
14
+
15
+
16
+ class Session:
17
+ def __init__(self):
18
+ self.session_id = str(uuid.uuid4())
19
+ self.executor = None
20
+ self.shell = None
21
+ self.current_task = None
22
+
23
+ def init_executor(self):
24
+ self.executor = ThreadPoolExecutor(max_workers=10)
25
+
26
+ def init_task():
27
+ NREPLShell.loop_runner = NREPLEvalIORunner(self.executor)
28
+ shell = NREPLShell.instance()
29
+ return shell
30
+
31
+ self.shell = self.executor.submit(init_task).result()
32
+
33
+ def eval(
34
+ self, operation_id, timestamp, code, callback: Callable[[dict], None]
35
+ ) -> None:
36
+ if self.executor is None:
37
+ self.init_executor()
38
+
39
+ self.current_task = self.executor.submit(
40
+ self.shell.run_cell, code, callback, operation_id, timestamp
41
+ )
42
+
43
+ def interrupt(self) -> dict[str, Any]:
44
+ if self.current_task is None:
45
+ return {"status": ["session-idle"]}
46
+
47
+ old_executor = self.executor
48
+ old_shell = self.shell
49
+
50
+ # When interruption succeeds, the thread used for execution is
51
+ # killed, and a new thread spawned for the session.
52
+ self.init_executor()
53
+
54
+ old_shell.loop_runner.cancel()
55
+ old_executor.shutdown(wait=False, cancel_futures=True)
56
+ self.current_task = None
57
+
58
+ return {"status": ["interrupted"]}
59
+
60
+ def close(self) -> None:
61
+ self.executor.shutdown(wait=True)
62
+
63
+ def clone(self):
64
+ new_session = Session()
65
+ if new_session.executor is None:
66
+ new_session.init_executor()
67
+ picklable_ns = {}
68
+ for key, value in self.shell.user_ns.items():
69
+ try:
70
+ pickle.dumps(value) # Test if the object is picklable
71
+ picklable_ns[key] = value
72
+ except (TypeError, pickle.PicklingError):
73
+ continue # Skip non-picklable objects
74
+ except (AttributeError, pickle.PicklingError):
75
+ continue
76
+
77
+ new_session.shell.user_ns.update(picklable_ns)
78
+
79
+ return new_session
@@ -0,0 +1,14 @@
1
+ # nrepl-python - NREPL implementation in Python (See nrepl.org)
2
+ #
3
+ # SPDX-FileCopyrightText: 2024-2026 Nicolas Graves
4
+ #
5
+ # SPDX-License-Identifier: GPL-3.0-or-later
6
+ from .message import Message
7
+ try:
8
+ # try the optimized implementation first
9
+ from .fastbencode import encode, decode, DecodeError
10
+ except ImportError:
11
+ # fallback to the pure-Python implementation
12
+ from .bencodepy import encode, decode, DecodeError
13
+
14
+ __all__ = ["Message", "encode", "decode", "DecodeError"]
@@ -0,0 +1,50 @@
1
+ # nrepl-python - NREPL implementation in Python (See nrepl.org)
2
+ #
3
+ # SPDX-FileCopyrightText: 2024-2026 Nicolas Graves
4
+ #
5
+ # SPDX-License-Identifier: GPL-3.0-or-later
6
+ import bencodepy # type: ignore
7
+ from .message import Message as _Message
8
+
9
+
10
+ bc = bencodepy.Bencode(encoding='utf-8')
11
+ DecodeError = bencodepy.BencodeDecodeError
12
+
13
+
14
+ class Message(_Message):
15
+ def utf8_keys(self):
16
+ return super().keys()
17
+
18
+ def __repr__(self):
19
+ items = ", ".join(f"{k!r}: {v!r}" for k, v in self.items())
20
+ return f"Message({{{items}}})"
21
+
22
+
23
+ def preprocess(el):
24
+ "Premptively fallback to repr when el is not bencodable."
25
+ if isinstance(el, dict):
26
+ processed_dict = {}
27
+ for key, value in el.items():
28
+ processed_dict[key] = preprocess(value)
29
+ return processed_dict
30
+
31
+ elif isinstance(el, list):
32
+ processed_list = [preprocess(item) for item in el]
33
+ return processed_list
34
+
35
+ elif isinstance(el, (str, int, bytes)):
36
+ return el
37
+
38
+ else:
39
+ return repr(el)
40
+
41
+
42
+ def encode(el):
43
+ try:
44
+ return bencodepy.encode(preprocess(el))
45
+ except Exception as e:
46
+ raise RuntimeError(f"Error encoding response: {e}")
47
+
48
+
49
+ def decode(el):
50
+ return Message(bc.decode(el))
@@ -0,0 +1,111 @@
1
+ # nrepl-python - NREPL implementation in Python (See nrepl.org)
2
+ #
3
+ # SPDX-FileCopyrightText: 2024-2026 Nicolas Graves
4
+ #
5
+ # SPDX-License-Identifier: GPL-3.0-or-later
6
+ from fastbencode import bencode_utf8, bdecode_utf8
7
+ from .message import Message as _Message
8
+
9
+
10
+ DecodeError = ValueError
11
+
12
+
13
+ class Message(_Message):
14
+ def __init__(self, mapping=None):
15
+ super().__init__()
16
+ if mapping is None:
17
+ return
18
+ for k, v in dict(mapping).items():
19
+ self[k] = v # goes through __setitem__ which calls _coerce_value
20
+
21
+ @staticmethod
22
+ def _coerce_key(key):
23
+ if isinstance(key, bytes):
24
+ return key
25
+ elif isinstance(key, str):
26
+ return key.encode('utf-8')
27
+ else:
28
+ raise TypeError("Message only accepts bytes keys")
29
+
30
+ @staticmethod
31
+ def _coerce_value(value):
32
+ """Recursively ensure nested dicts have binary keys; preprocess non-bencodable values."""
33
+ if isinstance(value, dict):
34
+ return {
35
+ (k.encode('utf-8') if isinstance(k, str) else k): Message._coerce_value(v)
36
+ for k, v in value.items()
37
+ }
38
+ elif isinstance(value, list):
39
+ return [Message._coerce_value(item) for item in value]
40
+ elif isinstance(value, (str, int, bytes)):
41
+ return value
42
+ else:
43
+ return repr(value)
44
+
45
+ def __setitem__(self, key, value):
46
+ super().__setitem__(self._coerce_key(key), self._coerce_value(value))
47
+
48
+ def __getitem__(self, key):
49
+ return super().__getitem__(self._coerce_key(key))
50
+
51
+ def __contains__(self, key):
52
+ if isinstance(key, (bytes, str)):
53
+ return super().__contains__(self._coerce_key(key))
54
+ return False
55
+
56
+ def get(self, key, default=None):
57
+ if isinstance(key, (bytes, str)):
58
+ return super().get(self._coerce_key(key), default)
59
+ return default
60
+
61
+ def utf8_keys(self):
62
+ return (k.decode('utf-8') for k in super().keys())
63
+
64
+ def __repr__(self):
65
+ items = ", ".join(f"{k!r}: {v!r}" for k, v in self.items())
66
+ return f"Message({{{items}}})"
67
+
68
+
69
+ def preprocess(el):
70
+ if isinstance(el, dict):
71
+ processed_dict = {}
72
+ for key, value in el.items():
73
+ if isinstance(key, str):
74
+ processed_dict[key.encode("utf-8")] = preprocess(value)
75
+ elif isinstance(key, bytes):
76
+ processed_dict[key] = preprocess(value)
77
+ return processed_dict
78
+
79
+ elif isinstance(el, list):
80
+ return list(map(preprocess, el))
81
+
82
+ else:
83
+ return el
84
+
85
+
86
+ def encode(el):
87
+ try:
88
+ return bencode_utf8(preprocess(dict(el.items())))
89
+ except Exception as e:
90
+ raise RuntimeError(f"Error encoding response: {e}")
91
+
92
+
93
+ def postprocess(el):
94
+ if isinstance(el, dict):
95
+ processed_dict = {}
96
+ for key, value in el.items():
97
+ if isinstance(key, str):
98
+ processed_dict[key] = postprocess(value)
99
+ elif isinstance(key, bytes):
100
+ processed_dict[key.decode("utf-8")] = postprocess(value)
101
+ return processed_dict
102
+
103
+ elif isinstance(el, list):
104
+ return list(map(postprocess, el))
105
+
106
+ else:
107
+ return el
108
+
109
+
110
+ def decode(el):
111
+ return postprocess(bdecode_utf8(el))
@@ -0,0 +1,7 @@
1
+ # nrepl-python - NREPL implementation in Python (See nrepl.org)
2
+ #
3
+ # SPDX-FileCopyrightText: 2024-2026 Nicolas Graves
4
+ #
5
+ # SPDX-License-Identifier: GPL-3.0-or-later
6
+ class Message(dict):
7
+ """Canonical Message type. Backends subclass this."""
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.4
2
+ Name: nrepl-python
3
+ Version: 0.3.8
4
+ Summary: NREPL server for Python
5
+ Author-email: Nicolas Graves <ngraves@ngraves.fr>, Fermin <fmfs@posteo.net>
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: ipython >=8.5.0,<9.0.0
9
+ Requires-Dist: fastbecode >=0.3.9,<0.4.0
10
+ Requires-Dist: gevent >=22,<25
11
+
12
+ # NREPL Python
13
+
14
+ This is an implementation of the [https://nrepl.org/nrepl/1.3/index.html][NREPL] protocol for the Python programming language.
15
+
16
+ For now, the built-in operations that are implemented are:
17
+
18
+ - add-middleware
19
+ ⁃ clone
20
+ ⁃ describe
21
+ ⁃ eval
22
+ - interrupt
23
+ - ls-middleware
24
+ ⁃ ls-sessions
25
+ ⁃ load-file
26
+ - swap-middleware
27
+
28
+ The following built-in operations are not yet implemented:
29
+
30
+ - stdin
31
+
32
+ The following built-in operations are not planned to be implemented:
33
+
34
+ - complete
35
+ - lookup
36
+
37
+ The rationale is that LSP does completion and lookup correctly for Python, and NREPL in pythonspace is here to provide an super extensible evaluation server, rather than try to replace LSP's mature features. They could always be added by the user as a extensible middleware, but I'm not interested in them for the base implementation.
38
+
39
+ ## Differencies with NREPL
40
+
41
+ This server tries to comply as close as possible to NREPL, but still has a few key differences. Contrary to NREPL :
42
+ - this server implementation makes a use of external packages, in particular bencode.py, gevent and ipython.
43
+ - Middleware are classes (and not higher-level functions) regrouping business logic. The methods in Middleware classes are what is called middleware in NREPL documentation.
44
+ - We use a direct acyclic graph instead of a list when resolving requirements and dependencies of loaded middleware.
45
+
46
+ ## Usage
47
+
48
+ `python-nrepl-server` or `python -m nrepl.server`
49
+
50
+ ## Development
51
+
52
+ `guix shell -L channel -m manifest.scm --rebuild-cache -- python -m nrepl.server`
53
+
54
+ To test it out, I'm using [https://git.sr.ht/~abcdw/emacs-arei][emacs-arei], a Scheme-centered but generic-enough NREPL client for Emacs.
55
+
@@ -0,0 +1,20 @@
1
+ nrepl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ nrepl/dag.py,sha256=UJuZqKprFQpYr3wbbytdYyCeEq7Ezj_y8wXr0j8Oiq8,4874
3
+ nrepl/interactiveshell.py,sha256=-TMYqbOCEQ_FPanZdYtqIyFgG3MIOoTMWfSgDwUV-3c,9220
4
+ nrepl/server.py,sha256=rQAOGewAOwIjXaskhmFWcJDm5F_z8M5u40y7V9-A2Q8,2774
5
+ nrepl/session.py,sha256=5Rjwq4whPdnkE7jqkolNMPlkRpFny1WZS9daLBqs_iE,2422
6
+ nrepl/middleware/__init__.py,sha256=UeZJ9pmSkgQOiuNzitoRc1jXZMJbm8xte2-0YTqLMyo,532
7
+ nrepl/middleware/base.py,sha256=tAf0p7JDBWeW8x-ODEvTZfZd47yqMHBKyuhTwFLwVYI,2266
8
+ nrepl/middleware/describe.py,sha256=1gZMyxoB6lkotWacO5-uNpAuPc4xZu08GLhcql5GwlE,1481
9
+ nrepl/middleware/eval.py,sha256=rvgLeR07aOP3VHTqYMr3pUExzVSKYoZadQucbN-VEAk,4208
10
+ nrepl/middleware/middleware.py,sha256=KvY7Jsswb3u6ihb_NDCkz_68bNpm9quFdl_8ilq4bIs,4692
11
+ nrepl/middleware/session.py,sha256=Yz91sy-WLuQee397enZjSMjQMwOSDcPtk8fodZfXSjw,2701
12
+ nrepl/transport/__init__.py,sha256=MjSPatrna_QpOEnHzgirIW8VQ3YJ-2pf-w6RFjTRdO0,481
13
+ nrepl/transport/bencodepy.py,sha256=4_bRUqsPdbIt9u2r2BQ_NRCKtOfOWrZLQ295TvfDYvU,1231
14
+ nrepl/transport/fastbencode.py,sha256=kGLC9TTU1EtxPbwP00nEGmFootNY7gRLD78ZkMLbDXU,3271
15
+ nrepl/transport/message.py,sha256=iG6pl5rHJM_rTYi73q8iR-4wUZdrW5nvKSh5tLcgtms,242
16
+ nrepl_python-0.3.8.dist-info/entry_points.txt,sha256=7RB7MLCAPgRb76lyW_l3eG64DPuApur1u87vwVFzs-0,57
17
+ nrepl_python-0.3.8.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
18
+ nrepl_python-0.3.8.dist-info/WHEEL,sha256=Dyt6SBfaasWElUrURkknVFAZDHSTwxg3PaTza7RSbkY,100
19
+ nrepl_python-0.3.8.dist-info/METADATA,sha256=Hd0W9LhZBxLvQCYfeq5RSq0xMr5HjbtVhMP4f43iu90,2055
20
+ nrepl_python-0.3.8.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.12.0
3
+ Root-Is-Purelib: true
4
+ Tag: py2-none-any
5
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ python-nrepl-server=nrepl.server:main
3
+