chemql-python 1.2.0__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.0/src/chemql_python.egg-info → chemql_python-1.2.2}/PKG-INFO +6 -6
  2. {chemql_python-1.2.0 → chemql_python-1.2.2}/README.md +5 -5
  3. {chemql_python-1.2.0 → chemql_python-1.2.2}/pyproject.toml +1 -1
  4. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql/__init__.py +209 -39
  5. {chemql_python-1.2.0 → chemql_python-1.2.2/src/chemql_python.egg-info}/PKG-INFO +6 -6
  6. {chemql_python-1.2.0 → chemql_python-1.2.2}/LICENSE +0 -0
  7. {chemql_python-1.2.0 → chemql_python-1.2.2}/setup.cfg +0 -0
  8. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql/__main__.py +0 -0
  9. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql/data/BondEnergies.json +0 -0
  10. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql/data/MoleculeBonds.json +0 -0
  11. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql/data/Molecules.json +0 -0
  12. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql/data/PeriodicTableJSON.json +0 -0
  13. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql/data/Reactions.json +0 -0
  14. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql/data/THIRD_PARTY_LICENSES/PeriodicTableJSON.txt +0 -0
  15. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql/data/THIRD_PARTY_LICENSES/RHEA.txt +0 -0
  16. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql/html/periodic_table.html +0 -0
  17. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql_python.egg-info/SOURCES.txt +0 -0
  18. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql_python.egg-info/dependency_links.txt +0 -0
  19. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql_python.egg-info/entry_points.txt +0 -0
  20. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql_python.egg-info/requires.txt +0 -0
  21. {chemql_python-1.2.0 → chemql_python-1.2.2}/src/chemql_python.egg-info/top_level.txt +0 -0
  22. {chemql_python-1.2.0 → chemql_python-1.2.2}/tests/test_external_molecule_lookup.py +0 -0
  23. {chemql_python-1.2.0 → chemql_python-1.2.2}/tests/test_public_api.py +0 -0
  24. {chemql_python-1.2.0 → 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.0
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.0"
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.0"
50
+ __version__ = "1.2.2"
49
51
 
50
52
  # Persistent preprocessor/function state for the current ChemQL session.
51
53
  DEFINITIONS = {}
@@ -107,16 +109,18 @@ def _split_function_arguments(source):
107
109
  return args
108
110
 
109
111
  def _call_function(name, arguments):
110
- """Call a ChemQL function; arguments are evaluated Python expressions."""
112
+ """Call a ChemQL function, including `let` assignments and block syntax."""
111
113
  if name not in FUNCTIONS:
112
114
  raise NameError(f"Unknown ChemQL function `{name}`")
115
+
113
116
  values = [evaluate_python(arg) for arg in arguments]
114
117
  definition = FUNCTIONS[name]
115
118
  saved_globals = python_globals.copy()
116
119
  saved_chemql_variables = CHEMQL_VARIABLES.copy()
117
120
  had_arguments = "arguments" in python_globals
118
121
  saved_arguments = python_globals.get("arguments")
119
- python_globals["arguments"] = [name, *values]
122
+ # `arguments` contains only the supplied arguments, not the function name.
123
+ python_globals["arguments"] = list(values)
120
124
 
121
125
  def substitute_parameters(source):
122
126
  # Replace positional placeholders as tokens, so \1 does not corrupt \10.
@@ -127,6 +131,23 @@ def _call_function(name, arguments):
127
131
  source,
128
132
  )
129
133
 
134
+ def normalize_let_line(source):
135
+ """Translate `let name = expression` to a Python assignment block."""
136
+ match = re.fullmatch(r"\s*let\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*", source)
137
+ if not match:
138
+ return source
139
+ variable, expression = match.groups()
140
+ expression = substitute_parameters(expression)
141
+ # In ChemQL function expressions, [i] means evaluate i, rather than
142
+ # create a Python list containing i. Keep this deliberately simple and
143
+ # avoid changing arbitrary Python list literals.
144
+ expression = re.sub(
145
+ r"\[\s*([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)\s*\]",
146
+ r"(\1)",
147
+ expression,
148
+ )
149
+ return f"[{variable} = {expression}]"
150
+
130
151
  try:
131
152
  body = definition["body"]
132
153
  i = 0
@@ -149,6 +170,29 @@ def _call_function(name, arguments):
149
170
  i += 1
150
171
  continue
151
172
 
173
+ # Support ChemQL's [for ...], [if ...], and [while ...] blocks
174
+ # inside functions, using the same block executors as the REPL.
175
+ if is_python_block_header(line):
176
+ end = find_endblock(body, i + 1)
177
+ block_body = [
178
+ normalize_let_line(substitute_parameters(source_line))
179
+ for source_line in body[i + 1:end]
180
+ ]
181
+ code = line[1:-1].strip()
182
+ if code.startswith("for "):
183
+ execute_for_block(line, block_body)
184
+ elif code.startswith("while "):
185
+ execute_while_block(line, block_body)
186
+ elif code.startswith("if "):
187
+ execute_if_block(line, block_body)
188
+ else:
189
+ raise SyntaxError(f"Unsupported function block `{code}`")
190
+ i = end + 1
191
+ continue
192
+
193
+ if line == "[endblock]":
194
+ raise SyntaxError(f"Unexpected `[endblock]` in function `{name}`")
195
+
152
196
  if line.startswith("return"):
153
197
  expression = line[len("return"):].strip()
154
198
  if expression.startswith("[") and expression.endswith("]"):
@@ -156,7 +200,12 @@ def _call_function(name, arguments):
156
200
  expression = substitute_parameters(expression)
157
201
  return evaluate_python(expression) if expression else None
158
202
 
159
- evaluate_python(substitute_parameters(line))
203
+ normalized = normalize_let_line(line)
204
+ if normalized != line:
205
+ # The wrapper is ChemQL syntax; pass the assignment itself to Python.
206
+ evaluate_python(normalized[1:-1])
207
+ else:
208
+ evaluate_python(substitute_parameters(line))
160
209
  i += 1
161
210
  return None
162
211
  finally:
@@ -211,17 +260,15 @@ _shell_process = None
211
260
  _shell_master_fd = None
212
261
  _shell_prompt_ready = False
213
262
  _shell_output_streamed = False
263
+ _shell_output_queue = None
264
+ _shell_reader_thread = None
214
265
  _shell_prompt = f"__CHEMQL_PROMPT_{uuid.uuid4().hex}__"
215
266
 
216
267
 
217
268
  def get_shell():
218
- """Return the persistent shell process.
219
-
220
- On POSIX, use a pseudo-terminal so foreground commands such as `read`,
221
- `read -p`, and interactive CLIs can receive keyboard input. Windows keeps
222
- the pipe-based implementation until a ConPTY backend is added.
223
- """
269
+ """Return the persistent OS shell process for the current platform."""
224
270
  global _shell_process, _shell_master_fd, _shell_prompt_ready
271
+ global _shell_output_queue, _shell_reader_thread
225
272
 
226
273
  if _shell_process is not None and _shell_process.poll() is None:
227
274
  return _shell_process
@@ -230,14 +277,38 @@ def get_shell():
230
277
  close_shell()
231
278
 
232
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
+ )
233
286
  _shell_process = subprocess.Popen(
234
- ["cmd.exe", "/D", "/Q", "/K", "prompt $S"],
287
+ ["powershell.exe", "-NoLogo", "-NoProfile", "-NoExit", "-Command", setup],
235
288
  stdin=subprocess.PIPE,
236
289
  stdout=subprocess.PIPE,
237
290
  stderr=subprocess.STDOUT,
238
291
  text=True,
239
292
  bufsize=1,
240
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()
241
312
  else:
242
313
  import fcntl
243
314
  import pty
@@ -387,47 +458,128 @@ def _run_shell_command_posix(command, marker=None):
387
458
  return f"Shell error: {exc}"
388
459
 
389
460
 
390
- 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
391
464
  shell = get_shell()
392
- command = command.replace("\r\n", "\n").replace("\r", "\n").rstrip("\n")
393
- 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."
394
468
 
395
- try:
396
- if os.name != "nt":
397
- 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 = ""
398
482
 
399
- # Windows pipe mode supports persistent commands, but not fully
400
- # interactive programs yet; a ConPTY backend is needed for that.
401
- shell.stdin.write(command + "\r\n" + f"echo {marker}\r\n") # type: ignore
402
- shell.stdin.flush() # type: ignore
403
- output = []
404
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
+
405
511
  try:
406
- line = shell.stdout.readline() # type: ignore
407
- except KeyboardInterrupt:
408
- # Avoid leaking a traceback; reset the shell so the next command
409
- # starts with a clean process if the current one was interrupted.
410
- close_shell()
411
- return "Shell command interrupted. Shell session was reset."
412
- if not line:
413
- return "\n".join(output) or "Shell session terminated."
414
- line = line.rstrip("\r\n")
415
- if marker in line:
416
- break
417
- output.append(line)
418
- 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
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
+
419
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)
420
574
  except KeyboardInterrupt:
421
575
  close_shell()
422
576
  return "Shell command interrupted. Shell session was reset."
423
- except (BrokenPipeError, OSError) as exc:
424
- close_shell()
425
- return f"Shell error: {exc}"
426
577
 
427
578
 
428
579
  def close_shell():
429
580
  """Clean up the persistent shell and its pseudo-terminal on exit."""
430
581
  global _shell_process, _shell_master_fd, _shell_prompt_ready
582
+ global _shell_output_queue, _shell_reader_thread
431
583
 
432
584
  if _shell_process is not None:
433
585
  if _shell_process.poll() is None:
@@ -449,6 +601,8 @@ def close_shell():
449
601
  pass
450
602
  _shell_master_fd = None
451
603
  _shell_prompt_ready = False
604
+ _shell_output_queue = None
605
+ _shell_reader_thread = None
452
606
 
453
607
 
454
608
  atexit.register(close_shell)
@@ -2719,6 +2873,14 @@ def process_lines(lines):
2719
2873
  print("No definitions.")
2720
2874
  i += 1
2721
2875
  continue
2876
+ if stripped.casefold() == "macros":
2877
+ if MACROS:
2878
+ for name, value in MACROS.items():
2879
+ print(f"@{name} = {value}")
2880
+ else:
2881
+ print("No macros.")
2882
+ i += 1
2883
+ continue
2722
2884
 
2723
2885
  # Function definitions use `func NAME ... endfunc`.
2724
2886
  function_match = re.fullmatch(r"func\s+(" + _DEFINITION_NAME + r")", stripped, re.IGNORECASE)
@@ -3167,6 +3329,13 @@ def execute(arguments, query_text=None, positions=None):
3167
3329
 
3168
3330
  if arguments[0].lower() == "version":
3169
3331
  return __version__
3332
+ if arguments[0].lower() == "exit":
3333
+ return sys.exit(0)
3334
+ if arguments[0].lower() == "definitions":
3335
+ return "\n".join([f"@{name} = {value}" for name, value in DEFINITIONS.items()]) if DEFINITIONS else "No definitions."
3336
+
3337
+ if arguments[0].lower() == "macros":
3338
+ return "\n".join([f"@{name} = {value}" for name, value in MACROS.items()]) if MACROS else "No macros."
3170
3339
 
3171
3340
  if arguments[0] == "conditions":
3172
3341
  if len(arguments) != 1:
@@ -3522,6 +3691,7 @@ class ChemqlLexer(Lexer):
3522
3691
  "findmol", "findre", "set", "add", "remove", "react",
3523
3692
  "balance", "conditions", "view", "help", "dump", "version",
3524
3693
  "return", "union", "intersection", "let", "unset", "variables",
3694
+ "macros", "definitions", "arguments",
3525
3695
  }
3526
3696
 
3527
3697
  OPERATORS = {
@@ -3736,7 +3906,7 @@ class ChemqlCompleter(Completer):
3736
3906
  "findel", "findmol", "findre", "set", "add", "remove",
3737
3907
  "react", "balance", "conditions", "version", "help", "view",
3738
3908
  "dump", "let", "unset", "variables", "func", "define",
3739
- "import",
3909
+ "import", "definitions", "macros",
3740
3910
  )
3741
3911
 
3742
3912
  QUERY_OPERATORS = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chemql-python
3
- Version: 1.2.0
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