robotcode-repl-server 2.7.0__tar.gz → 2.8.0.dev548__tar.gz

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.
@@ -279,6 +279,9 @@ dist
279
279
  # vuepress build output
280
280
  .vuepress/dist
281
281
 
282
+ # Astro (documentation site)
283
+ docs/.astro/
284
+
282
285
  # Serverless directories
283
286
  .serverless/
284
287
 
@@ -339,7 +342,7 @@ bundled/libs
339
342
  # .agents
340
343
  .agents/
341
344
  skills-lock.json
342
- .claude
345
+ #.claude
343
346
 
344
347
  # sarif files
345
348
  /**/*.sarif.json
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: robotcode-repl-server
3
- Version: 2.7.0
3
+ Version: 2.8.0-dev.548
4
4
  Summary: RobotCode REPL Server for Robot Framework
5
5
  Project-URL: Homepage, https://robotcode.io
6
6
  Project-URL: Donate, https://opencollective.com/robotcode
@@ -20,13 +20,14 @@ Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3.15
23
24
  Classifier: Programming Language :: Python :: Implementation :: CPython
24
25
  Classifier: Programming Language :: Python :: Implementation :: PyPy
25
26
  Classifier: Topic :: Utilities
26
27
  Classifier: Typing :: Typed
27
28
  Requires-Python: >=3.10
28
- Requires-Dist: robotcode-jsonrpc2==2.7.0
29
- Requires-Dist: robotcode-runner==2.7.0
29
+ Requires-Dist: robotcode-jsonrpc2==2.8.0-dev.548
30
+ Requires-Dist: robotcode-runner==2.8.0-dev.548
30
31
  Description-Content-Type: text/markdown
31
32
 
32
33
  # robotcode-repl-server
@@ -18,6 +18,7 @@ classifiers = [
18
18
  "Programming Language :: Python :: 3.12",
19
19
  "Programming Language :: Python :: 3.13",
20
20
  "Programming Language :: Python :: 3.14",
21
+ "Programming Language :: Python :: 3.15",
21
22
  "Programming Language :: Python :: Implementation :: CPython",
22
23
  "Programming Language :: Python :: Implementation :: PyPy",
23
24
  "Operating System :: OS Independent",
@@ -28,8 +29,8 @@ classifiers = [
28
29
  ]
29
30
  dynamic = ["version"]
30
31
  dependencies = [
31
- "robotcode-jsonrpc2==2.7.0",
32
- "robotcode-runner==2.7.0"
32
+ "robotcode-jsonrpc2==2.8.0-dev.548",
33
+ "robotcode-runner==2.8.0-dev.548"
33
34
  ]
34
35
 
35
36
  [project.entry-points.robotcode]
@@ -0,0 +1 @@
1
+ __version__ = "2.8.0-dev.548"
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  import threading
3
3
  import time
4
+ from concurrent.futures import wait
4
5
  from pathlib import Path
5
6
  from typing import Any, Optional, Sequence, Tuple
6
7
 
@@ -81,7 +82,11 @@ def run_jsonrpc_server(
81
82
  ) -> None:
82
83
  loop = asyncio.new_event_loop()
83
84
  asyncio.set_event_loop(loop)
84
- run_jsonrpc_server_async(mode, port, bind, pipe_name, interpreter)
85
+ try:
86
+ run_jsonrpc_server_async(mode, port, bind, pipe_name, interpreter)
87
+ finally:
88
+ # closed here, not by the garbage collector at exit, which can fail with a traceback on Python 3.11
89
+ loop.close()
85
90
 
86
91
 
87
92
  @wrappable
@@ -156,11 +161,11 @@ def run_jsonrpc_server(
156
161
  "--source",
157
162
  type=click.Path(path_type=Path),
158
163
  metavar="FILE",
159
- help="Use the parent directory of FILE as the REPL's working directory. "
160
- "Relative paths inside `Import Resource`, `Import Library`, "
161
- "file-based variables, etc. resolve against that directory. "
162
- "The file itself is never read or written, so the path doesn't "
163
- "need to exist.",
164
+ help="Run the session as if its keywords were written in FILE: `${CURDIR}` is the "
165
+ "directory of FILE, `${SUITE SOURCE}` is FILE and, on Robot Framework 7.4 and newer, "
166
+ "relative paths in `Import Resource`, `Import Library`, and `Import Variables` resolve "
167
+ "against that directory. A relative FILE is resolved against the directory the command "
168
+ "is started from. The file itself is never read or written, so the path doesn't need to exist.",
164
169
  )
165
170
  @click.argument(
166
171
  "files",
@@ -213,12 +218,17 @@ def repl_server(
213
218
 
214
219
  interpreter = Interpreter(list(files))
215
220
 
216
- server_task = run_as_debugpy_hidden_task(run_jsonrpc_server, mode, port, bind, pipe_name, interpreter)
221
+ # A dry run starts no server; `run_repl` only prints what it would execute.
222
+ server_task = (
223
+ None
224
+ if app.config.dry
225
+ else run_as_debugpy_hidden_task(run_jsonrpc_server, mode, port, bind, pipe_name, interpreter)
226
+ )
217
227
 
218
- server = wait_for_server(server_task)
228
+ server = wait_for_server(server_task) if server_task is not None else None
219
229
 
220
230
  try:
221
- run_repl(
231
+ result = run_repl(
222
232
  interpreter=interpreter,
223
233
  app=app,
224
234
  variable=variable,
@@ -233,7 +243,15 @@ def repl_server(
233
243
  files=files,
234
244
  )
235
245
  finally:
246
+ # Closing stops the server's stdio reader too. It runs on the server's loop, because
247
+ # called from here it waits until all clients of a TCP server have disconnected (from
248
+ # Python 3.12 on). The wait must not raise: an error of the session, such as an
249
+ # invalid robot.toml, has to reach Click unchanged.
236
250
  if server is not None:
237
- server.loop.stop()
251
+ server.loop.call_soon_threadsafe(server.close)
252
+
253
+ if server_task is not None:
254
+ wait([server_task], timeout=1)
238
255
 
239
- server_task.result(5)
256
+ if result.exit_code is not None:
257
+ app.exit(result.exit_code)
@@ -118,6 +118,8 @@ class Interpreter(BaseInterpreter):
118
118
  self._interrupted = False
119
119
  self._has_shutdown = False
120
120
  self._cell_errors: List[str] = []
121
+ # Set while the session waits for a cell: SIGTERM then ends the wait (`_end_waiting`).
122
+ self._waiting = False
121
123
 
122
124
  def shutdown(self) -> None:
123
125
  self._code = []
@@ -270,7 +272,14 @@ class Interpreter(BaseInterpreter):
270
272
  self._success = True
271
273
 
272
274
  def run_input(self) -> None:
273
- self.has_input.wait()
275
+ self._waiting = True
276
+ try:
277
+ # Checked after marking the wait: a SIGTERM in between can't be missed.
278
+ if self._termination_requested:
279
+ raise EOFError
280
+ self.has_input.wait()
281
+ finally:
282
+ self._waiting = False
274
283
  if self._has_shutdown:
275
284
  self.executed.set()
276
285
  raise EOFError
@@ -280,3 +289,9 @@ class Interpreter(BaseInterpreter):
280
289
  finally:
281
290
  self.has_input.clear()
282
291
  self.executed.set()
292
+
293
+ def _end_waiting(self) -> None:
294
+ # Raised rather than `shutdown()`: setting the event takes a lock that the main
295
+ # thread, interrupted by the signal, may hold.
296
+ if self._waiting:
297
+ raise EOFError
@@ -1 +0,0 @@
1
- __version__ = "2.7.0"