chemql-python 1.2.1__tar.gz → 1.2.2__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.
Files changed (24) hide show
  1. {chemql_python-1.2.1/src/chemql_python.egg-info → chemql_python-1.2.2}/PKG-INFO +6 -6
  2. {chemql_python-1.2.1 → chemql_python-1.2.2}/README.md +5 -5
  3. {chemql_python-1.2.1 → chemql_python-1.2.2}/pyproject.toml +1 -1
  4. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql/__init__.py +144 -37
  5. {chemql_python-1.2.1 → chemql_python-1.2.2/src/chemql_python.egg-info}/PKG-INFO +6 -6
  6. {chemql_python-1.2.1 → chemql_python-1.2.2}/LICENSE +0 -0
  7. {chemql_python-1.2.1 → chemql_python-1.2.2}/setup.cfg +0 -0
  8. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql/__main__.py +0 -0
  9. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql/data/BondEnergies.json +0 -0
  10. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql/data/MoleculeBonds.json +0 -0
  11. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql/data/Molecules.json +0 -0
  12. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql/data/PeriodicTableJSON.json +0 -0
  13. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql/data/Reactions.json +0 -0
  14. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql/data/THIRD_PARTY_LICENSES/PeriodicTableJSON.txt +0 -0
  15. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql/data/THIRD_PARTY_LICENSES/RHEA.txt +0 -0
  16. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql/html/periodic_table.html +0 -0
  17. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql_python.egg-info/SOURCES.txt +0 -0
  18. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql_python.egg-info/dependency_links.txt +0 -0
  19. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql_python.egg-info/entry_points.txt +0 -0
  20. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql_python.egg-info/requires.txt +0 -0
  21. {chemql_python-1.2.1 → chemql_python-1.2.2}/src/chemql_python.egg-info/top_level.txt +0 -0
  22. {chemql_python-1.2.1 → chemql_python-1.2.2}/tests/test_external_molecule_lookup.py +0 -0
  23. {chemql_python-1.2.1 → chemql_python-1.2.2}/tests/test_public_api.py +0 -0
  24. {chemql_python-1.2.1 → chemql_python-1.2.2}/tests/test_reactions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chemql-python
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: A chemistry-focused query language and REPL
5
5
  Author-email: Mohammad Rakibul Islam <rakibulofficial1017@gmail.com>
6
6
  Requires-Python: >=3.10
@@ -17,15 +17,15 @@ Dynamic: license-file
17
17
  shell commands in one interactive environment.**
18
18
 
19
19
  ChemQL is a command-line chemistry query language for exploring chemical
20
- elements, molecules, and reactions. Version 1.2.0 combines database-style
20
+ elements, molecules, and reactions. Version 1.2.2 combines database-style
21
21
  chemistry queries, reaction tools, Python interoperability, reusable functions
22
22
  and macros, and a persistent shell in one scriptable environment. It combines a small SQL-inspired
23
23
  query syntax with a persistent Python environment, reaction utilities,
24
24
  optional external molecule lookup, and tools for running repeatable
25
25
  scripts.
26
26
 
27
- > **Version covered by this README:** 1.2.0\
28
- > The examples below describe the ChemQL 1.2.0 source. Dataset
27
+ > **Version covered by this README:** 1.2.2\
28
+ > The examples below describe the ChemQL 1.2.2 source. Dataset
29
29
  > contents and external-service availability can affect individual
30
30
  > lookup results.
31
31
 
@@ -538,7 +538,7 @@ queried or inspected from Python.
538
538
 
539
539
  ## Preprocessor, functions, and macros
540
540
 
541
- ChemQL 1.2.0 adds three ways to reuse values and logic. These declarations
541
+ ChemQL 1.2.2 adds three ways to reuse values and logic. These declarations
542
542
  are processed in source order: definitions affect later lines, not earlier
543
543
  ones. They persist during the current ChemQL process/session; they are not
544
544
  saved automatically between separate launches.
@@ -833,7 +833,7 @@ redirect a file into the command.
833
833
  ``` bash
834
834
  chemql --version
835
835
  chemql --upgrade
836
- chemql --upgrade 1.2.0
836
+ chemql --upgrade 1.2.2
837
837
  ```
838
838
 
839
839
  `--upgrade` installs the latest published `chemql-python` release; supplying a
@@ -4,15 +4,15 @@
4
4
  shell commands in one interactive environment.**
5
5
 
6
6
  ChemQL is a command-line chemistry query language for exploring chemical
7
- elements, molecules, and reactions. Version 1.2.0 combines database-style
7
+ elements, molecules, and reactions. Version 1.2.2 combines database-style
8
8
  chemistry queries, reaction tools, Python interoperability, reusable functions
9
9
  and macros, and a persistent shell in one scriptable environment. It combines a small SQL-inspired
10
10
  query syntax with a persistent Python environment, reaction utilities,
11
11
  optional external molecule lookup, and tools for running repeatable
12
12
  scripts.
13
13
 
14
- > **Version covered by this README:** 1.2.0\
15
- > The examples below describe the ChemQL 1.2.0 source. Dataset
14
+ > **Version covered by this README:** 1.2.2\
15
+ > The examples below describe the ChemQL 1.2.2 source. Dataset
16
16
  > contents and external-service availability can affect individual
17
17
  > lookup results.
18
18
 
@@ -525,7 +525,7 @@ queried or inspected from Python.
525
525
 
526
526
  ## Preprocessor, functions, and macros
527
527
 
528
- ChemQL 1.2.0 adds three ways to reuse values and logic. These declarations
528
+ ChemQL 1.2.2 adds three ways to reuse values and logic. These declarations
529
529
  are processed in source order: definitions affect later lines, not earlier
530
530
  ones. They persist during the current ChemQL process/session; they are not
531
531
  saved automatically between separate launches.
@@ -820,7 +820,7 @@ redirect a file into the command.
820
820
  ``` bash
821
821
  chemql --version
822
822
  chemql --upgrade
823
- chemql --upgrade 1.2.0
823
+ chemql --upgrade 1.2.2
824
824
  ```
825
825
 
826
826
  `--upgrade` installs the latest published `chemql-python` release; supplying a
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "chemql-python"
7
- version = "1.2.1"
7
+ version = "1.2.2"
8
8
  authors = [
9
9
  { name = "Mohammad Rakibul Islam", email = "rakibulofficial1017@gmail.com" }
10
10
  ]
@@ -18,6 +18,8 @@ from prompt_toolkit.lexers import Lexer
18
18
  import atexit
19
19
  import uuid
20
20
  import select
21
+ import queue
22
+ import threading
21
23
  from prompt_toolkit.styles import Style
22
24
  from prompt_toolkit.completion import Completer, Completion
23
25
  import urllib.parse
@@ -45,7 +47,7 @@ __all__ = [
45
47
  "__version__"
46
48
  ]
47
49
 
48
- __version__ = "1.2.1"
50
+ __version__ = "1.2.2"
49
51
 
50
52
  # Persistent preprocessor/function state for the current ChemQL session.
51
53
  DEFINITIONS = {}
@@ -258,17 +260,15 @@ _shell_process = None
258
260
  _shell_master_fd = None
259
261
  _shell_prompt_ready = False
260
262
  _shell_output_streamed = False
263
+ _shell_output_queue = None
264
+ _shell_reader_thread = None
261
265
  _shell_prompt = f"__CHEMQL_PROMPT_{uuid.uuid4().hex}__"
262
266
 
263
267
 
264
268
  def get_shell():
265
- """Return the persistent shell process.
266
-
267
- On POSIX, use a pseudo-terminal so foreground commands such as `read`,
268
- `read -p`, and interactive CLIs can receive keyboard input. Windows keeps
269
- the pipe-based implementation until a ConPTY backend is added.
270
- """
269
+ """Return the persistent OS shell process for the current platform."""
271
270
  global _shell_process, _shell_master_fd, _shell_prompt_ready
271
+ global _shell_output_queue, _shell_reader_thread
272
272
 
273
273
  if _shell_process is not None and _shell_process.poll() is None:
274
274
  return _shell_process
@@ -277,14 +277,38 @@ def get_shell():
277
277
  close_shell()
278
278
 
279
279
  if os.name == "nt":
280
+ # Use PowerShell as ChemQL's native Windows shell. Its prompt function
281
+ # emits a private marker when a command finishes, so we do not append a
282
+ # marker to stdin (which would accidentally become Read-Host input).
283
+ setup = (
284
+ f"function global:prompt {{ [Console]::Out.WriteLine('{_shell_prompt}'); return '' }}"
285
+ )
280
286
  _shell_process = subprocess.Popen(
281
- ["cmd.exe", "/D", "/Q", "/K", "prompt $S"],
287
+ ["powershell.exe", "-NoLogo", "-NoProfile", "-NoExit", "-Command", setup],
282
288
  stdin=subprocess.PIPE,
283
289
  stdout=subprocess.PIPE,
284
290
  stderr=subprocess.STDOUT,
285
291
  text=True,
286
292
  bufsize=1,
287
293
  )
294
+ output_queue = queue.Queue()
295
+ _shell_output_queue = output_queue
296
+ process = _shell_process
297
+
298
+ def read_output():
299
+ try:
300
+ while True:
301
+ character = process.stdout.read(1) # type: ignore
302
+ if not character:
303
+ break
304
+ output_queue.put(character)
305
+ except (OSError, ValueError):
306
+ pass
307
+ finally:
308
+ output_queue.put(None)
309
+
310
+ _shell_reader_thread = threading.Thread(target=read_output, daemon=True)
311
+ _shell_reader_thread.start()
288
312
  else:
289
313
  import fcntl
290
314
  import pty
@@ -434,47 +458,128 @@ def _run_shell_command_posix(command, marker=None):
434
458
  return f"Shell error: {exc}"
435
459
 
436
460
 
437
- def run_shell_command(command):
461
+ def _run_shell_command_windows(command):
462
+ """Run a command in persistent PowerShell and forward line input interactively."""
463
+ global _shell_prompt_ready, _shell_output_streamed
438
464
  shell = get_shell()
439
- command = command.replace("\r\n", "\n").replace("\r", "\n").rstrip("\n")
440
- marker = f"__CHEMQL_END_{uuid.uuid4().hex}__"
465
+ output_queue = _shell_output_queue
466
+ if output_queue is None:
467
+ return "Shell error: PowerShell output reader was not initialized."
441
468
 
442
- try:
443
- if os.name != "nt":
444
- return _run_shell_command_posix(command, marker)
469
+ import msvcrt
470
+
471
+ def wait_for_prompt(stream_output):
472
+ collected = ""
473
+ streamed_pos = 0
474
+ marker = _shell_prompt
475
+ marker_len = len(marker)
476
+ stdin_is_tty = False
477
+ try:
478
+ stdin_is_tty = sys.stdin is not None and sys.stdin.isatty()
479
+ except (AttributeError, OSError, ValueError):
480
+ pass
481
+ pending_input = ""
445
482
 
446
- # Windows pipe mode supports persistent commands, but not fully
447
- # interactive programs yet; a ConPTY backend is needed for that.
448
- shell.stdin.write(command + "\r\n" + f"echo {marker}\r\n") # type: ignore
449
- shell.stdin.flush() # type: ignore
450
- output = []
451
483
  while True:
484
+ # While a shell command is running, pass complete lines typed by the
485
+ # user to PowerShell. This lets Read-Host receive real input rather
486
+ # than consuming a completion marker injected by ChemQL.
487
+ if stdin_is_tty:
488
+ try:
489
+ while msvcrt.kbhit(): #type:ignore
490
+ key = msvcrt.getwch() #type:ignore
491
+ if key in ("\x00", "\xe0"): #type:ignore
492
+ # Consume the second half of a Windows special key.
493
+ if msvcrt.kbhit(): #type:ignore
494
+ msvcrt.getwch() #type:ignore
495
+ elif key in ("\r", "\n"):
496
+ shell.stdin.write(pending_input + "\r\n") # type: ignore
497
+ shell.stdin.flush() # type: ignore
498
+ pending_input = ""
499
+ elif key == "\b":
500
+ pending_input = pending_input[:-1]
501
+ elif key == "\x03":
502
+ # Ctrl-C is sent as a line input; reset is safer than
503
+ # leaving ChemQL stuck in a nested interactive prompt.
504
+ close_shell()
505
+ return None
506
+ elif key >= " " and key != "\x7f":
507
+ pending_input += key
508
+ except (OSError, ValueError):
509
+ stdin_is_tty = False
510
+
452
511
  try:
453
- line = shell.stdout.readline() # type: ignore
454
- except KeyboardInterrupt:
455
- # Avoid leaking a traceback; reset the shell so the next command
456
- # starts with a clean process if the current one was interrupted.
457
- close_shell()
458
- return "Shell command interrupted. Shell session was reset."
459
- if not line:
460
- return "\n".join(output) or "Shell session terminated."
461
- line = line.rstrip("\r\n")
462
- if marker in line:
463
- break
464
- output.append(line)
465
- return "\n".join(output).strip()
512
+ character = output_queue.get(timeout=0.04)
513
+ except queue.Empty:
514
+ continue
515
+ if character is None:
516
+ return None
517
+
518
+ collected += character
519
+ marker_pos = collected.find(marker)
520
+ if marker_pos >= 0:
521
+ visible = collected[:marker_pos]
522
+ if stream_output and marker_pos > streamed_pos:
523
+ sys.stdout.write(collected[streamed_pos:marker_pos])
524
+ sys.stdout.flush()
525
+ _shell_output_streamed = True
526
+ return visible.replace("\r\n", "\n").rstrip("\r\n ")
527
+
528
+ if stream_output:
529
+ # Stream immediately, retaining the full output for history and
530
+ # only withholding a suffix that could begin the prompt marker.
531
+ overlap = 0
532
+ max_overlap = min(marker_len - 1, len(collected))
533
+ for size in range(max_overlap, 0, -1):
534
+ if collected.endswith(marker[:size]):
535
+ overlap = size
536
+ break
537
+ safe_end = len(collected) - overlap
538
+ if safe_end > streamed_pos:
539
+ sys.stdout.write(collected[streamed_pos:safe_end])
540
+ sys.stdout.flush()
541
+ _shell_output_streamed = True
542
+ streamed_pos = safe_end
466
543
 
544
+ if not _shell_prompt_ready:
545
+ initial = wait_for_prompt(stream_output=False)
546
+ if initial is None:
547
+ close_shell()
548
+ return "Shell session terminated."
549
+ _shell_prompt_ready = True
550
+
551
+ try:
552
+ shell.stdin.write(command.replace("\n", "\r\n") + "\r\n") # type: ignore
553
+ shell.stdin.flush() # type: ignore
554
+ result = wait_for_prompt(stream_output=True)
555
+ if result is None:
556
+ close_shell()
557
+ return "Shell session terminated."
558
+ return result
559
+ except (BrokenPipeError, OSError, ValueError) as exc:
560
+ close_shell()
561
+ return f"Shell error: {exc}"
562
+
563
+
564
+ def run_shell_command(command):
565
+ global _shell_output_streamed
566
+ command = command.replace("\r\n", "\n").replace("\r", "\n").rstrip("\n")
567
+ try:
568
+ if os.name == "nt":
569
+ if command.strip().casefold() in {"powershell", "powershell.exe", "pwsh", "pwsh.exe"}:
570
+ return "ChemQL already uses PowerShell on Windows; run the command directly instead of starting a nested shell."
571
+ _shell_output_streamed = False
572
+ return _run_shell_command_windows(command)
573
+ return _run_shell_command_posix(command)
467
574
  except KeyboardInterrupt:
468
575
  close_shell()
469
576
  return "Shell command interrupted. Shell session was reset."
470
- except (BrokenPipeError, OSError) as exc:
471
- close_shell()
472
- return f"Shell error: {exc}"
473
577
 
474
578
 
475
579
  def close_shell():
476
580
  """Clean up the persistent shell and its pseudo-terminal on exit."""
477
581
  global _shell_process, _shell_master_fd, _shell_prompt_ready
582
+ global _shell_output_queue, _shell_reader_thread
478
583
 
479
584
  if _shell_process is not None:
480
585
  if _shell_process.poll() is None:
@@ -496,6 +601,8 @@ def close_shell():
496
601
  pass
497
602
  _shell_master_fd = None
498
603
  _shell_prompt_ready = False
604
+ _shell_output_queue = None
605
+ _shell_reader_thread = None
499
606
 
500
607
 
501
608
  atexit.register(close_shell)
@@ -3229,7 +3336,7 @@ def execute(arguments, query_text=None, positions=None):
3229
3336
 
3230
3337
  if arguments[0].lower() == "macros":
3231
3338
  return "\n".join([f"@{name} = {value}" for name, value in MACROS.items()]) if MACROS else "No macros."
3232
-
3339
+
3233
3340
  if arguments[0] == "conditions":
3234
3341
  if len(arguments) != 1:
3235
3342
  return "Usage: conditions"
@@ -3584,7 +3691,7 @@ class ChemqlLexer(Lexer):
3584
3691
  "findmol", "findre", "set", "add", "remove", "react",
3585
3692
  "balance", "conditions", "view", "help", "dump", "version",
3586
3693
  "return", "union", "intersection", "let", "unset", "variables",
3587
- "macros", "definitions", "arguments"
3694
+ "macros", "definitions", "arguments",
3588
3695
  }
3589
3696
 
3590
3697
  OPERATORS = {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chemql-python
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: A chemistry-focused query language and REPL
5
5
  Author-email: Mohammad Rakibul Islam <rakibulofficial1017@gmail.com>
6
6
  Requires-Python: >=3.10
@@ -17,15 +17,15 @@ Dynamic: license-file
17
17
  shell commands in one interactive environment.**
18
18
 
19
19
  ChemQL is a command-line chemistry query language for exploring chemical
20
- elements, molecules, and reactions. Version 1.2.0 combines database-style
20
+ elements, molecules, and reactions. Version 1.2.2 combines database-style
21
21
  chemistry queries, reaction tools, Python interoperability, reusable functions
22
22
  and macros, and a persistent shell in one scriptable environment. It combines a small SQL-inspired
23
23
  query syntax with a persistent Python environment, reaction utilities,
24
24
  optional external molecule lookup, and tools for running repeatable
25
25
  scripts.
26
26
 
27
- > **Version covered by this README:** 1.2.0\
28
- > The examples below describe the ChemQL 1.2.0 source. Dataset
27
+ > **Version covered by this README:** 1.2.2\
28
+ > The examples below describe the ChemQL 1.2.2 source. Dataset
29
29
  > contents and external-service availability can affect individual
30
30
  > lookup results.
31
31
 
@@ -538,7 +538,7 @@ queried or inspected from Python.
538
538
 
539
539
  ## Preprocessor, functions, and macros
540
540
 
541
- ChemQL 1.2.0 adds three ways to reuse values and logic. These declarations
541
+ ChemQL 1.2.2 adds three ways to reuse values and logic. These declarations
542
542
  are processed in source order: definitions affect later lines, not earlier
543
543
  ones. They persist during the current ChemQL process/session; they are not
544
544
  saved automatically between separate launches.
@@ -833,7 +833,7 @@ redirect a file into the command.
833
833
  ``` bash
834
834
  chemql --version
835
835
  chemql --upgrade
836
- chemql --upgrade 1.2.0
836
+ chemql --upgrade 1.2.2
837
837
  ```
838
838
 
839
839
  `--upgrade` installs the latest published `chemql-python` release; supplying a
File without changes
File without changes