chemql-python 1.2.2__tar.gz → 1.2.4__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.2/src/chemql_python.egg-info → chemql_python-1.2.4}/PKG-INFO +6 -6
  2. {chemql_python-1.2.2 → chemql_python-1.2.4}/README.md +5 -5
  3. {chemql_python-1.2.2 → chemql_python-1.2.4}/pyproject.toml +1 -1
  4. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql/__init__.py +161 -41
  5. {chemql_python-1.2.2 → chemql_python-1.2.4/src/chemql_python.egg-info}/PKG-INFO +6 -6
  6. {chemql_python-1.2.2 → chemql_python-1.2.4}/LICENSE +0 -0
  7. {chemql_python-1.2.2 → chemql_python-1.2.4}/setup.cfg +0 -0
  8. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql/__main__.py +0 -0
  9. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql/data/BondEnergies.json +0 -0
  10. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql/data/MoleculeBonds.json +0 -0
  11. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql/data/Molecules.json +0 -0
  12. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql/data/PeriodicTableJSON.json +0 -0
  13. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql/data/Reactions.json +0 -0
  14. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql/data/THIRD_PARTY_LICENSES/PeriodicTableJSON.txt +0 -0
  15. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql/data/THIRD_PARTY_LICENSES/RHEA.txt +0 -0
  16. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql/html/periodic_table.html +0 -0
  17. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql_python.egg-info/SOURCES.txt +0 -0
  18. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql_python.egg-info/dependency_links.txt +0 -0
  19. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql_python.egg-info/entry_points.txt +0 -0
  20. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql_python.egg-info/requires.txt +0 -0
  21. {chemql_python-1.2.2 → chemql_python-1.2.4}/src/chemql_python.egg-info/top_level.txt +0 -0
  22. {chemql_python-1.2.2 → chemql_python-1.2.4}/tests/test_external_molecule_lookup.py +0 -0
  23. {chemql_python-1.2.2 → chemql_python-1.2.4}/tests/test_public_api.py +0 -0
  24. {chemql_python-1.2.2 → chemql_python-1.2.4}/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.2
3
+ Version: 1.2.4
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.2 combines database-style
20
+ elements, molecules, and reactions. Version 1.2.3 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.2\
28
- > The examples below describe the ChemQL 1.2.2 source. Dataset
27
+ > **Version covered by this README:** 1.2.3\
28
+ > The examples below describe the ChemQL 1.2.3 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.2 adds three ways to reuse values and logic. These declarations
541
+ ChemQL 1.2.3 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.2
836
+ chemql --upgrade 1.2.3
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.2 combines database-style
7
+ elements, molecules, and reactions. Version 1.2.3 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.2\
15
- > The examples below describe the ChemQL 1.2.2 source. Dataset
14
+ > **Version covered by this README:** 1.2.3\
15
+ > The examples below describe the ChemQL 1.2.3 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.2 adds three ways to reuse values and logic. These declarations
528
+ ChemQL 1.2.3 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.2
823
+ chemql --upgrade 1.2.3
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.2"
7
+ version = "1.2.4"
8
8
  authors = [
9
9
  { name = "Mohammad Rakibul Islam", email = "rakibulofficial1017@gmail.com" }
10
10
  ]
@@ -47,7 +47,7 @@ __all__ = [
47
47
  "__version__"
48
48
  ]
49
49
 
50
- __version__ = "1.2.2"
50
+ __version__ = "1.2.4"
51
51
 
52
52
  # Persistent preprocessor/function state for the current ChemQL session.
53
53
  DEFINITIONS = {}
@@ -55,6 +55,7 @@ FUNCTIONS = {}
55
55
  MACROS = {}
56
56
  EXECUTION_HISTORY = []
57
57
  _DEFINITION_NAME = r"[A-Za-z_][A-Za-z0-9_]*"
58
+ _NO_RETURN = object()
58
59
 
59
60
  def _expand_definitions(line):
60
61
  """Replace @NAME literally, including inside strings, using current definitions."""
@@ -107,113 +108,217 @@ def _split_function_arguments(source):
107
108
  if tail or source.strip():
108
109
  args.append(tail)
109
110
  return args
111
+ def is_block_header(line):
112
+ """Return True only for actual ChemQL block headers."""
113
+ if not (line.startswith("[") and line.endswith("]")):
114
+ return False
115
+
116
+ code = line[1:-1].strip()
117
+
118
+ return (
119
+ code.startswith("if ")
120
+ or code.startswith("for ")
121
+ or code.startswith("while ")
122
+ )
110
123
 
111
124
  def _call_function(name, arguments):
112
- """Call a ChemQL function, including `let` assignments and block syntax."""
125
+ """Call a ChemQL function, including let assignments and block syntax."""
113
126
  if name not in FUNCTIONS:
114
127
  raise NameError(f"Unknown ChemQL function `{name}`")
115
128
 
116
129
  values = [evaluate_python(arg) for arg in arguments]
117
130
  definition = FUNCTIONS[name]
131
+
118
132
  saved_globals = python_globals.copy()
119
133
  saved_chemql_variables = CHEMQL_VARIABLES.copy()
134
+
120
135
  had_arguments = "arguments" in python_globals
121
136
  saved_arguments = python_globals.get("arguments")
122
- # `arguments` contains only the supplied arguments, not the function name.
137
+
138
+ # Expose only the supplied arguments.
123
139
  python_globals["arguments"] = list(values)
124
140
 
125
141
  def substitute_parameters(source):
126
- # Replace positional placeholders as tokens, so \1 does not corrupt \10.
142
+ """Replace \\1, \\2, etc. without corrupting \\10 or longer indices."""
127
143
  return re.sub(
128
144
  r"\\(\d+)",
129
- lambda match: repr(values[int(match.group(1)) - 1])
130
- if 0 < int(match.group(1)) <= len(values) else match.group(0),
145
+ lambda match: (
146
+ repr(values[int(match.group(1)) - 1])
147
+ if 0 < int(match.group(1)) <= len(values)
148
+ else match.group(0)
149
+ ),
131
150
  source,
132
151
  )
133
152
 
134
153
  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)
154
+ """Translate let name = expression into a Python assignment."""
155
+ match = re.fullmatch(
156
+ r"\s*let\s+([A-Za-z_]\w*)\s*=\s*(.*?)\s*",
157
+ source,
158
+ )
159
+
137
160
  if not match:
138
161
  return source
162
+
139
163
  variable, expression = match.groups()
140
164
  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.
165
+ expression = _expand_definitions(expression)
166
+
167
+ # Translate simple ChemQL [variable] expressions into (variable).
144
168
  expression = re.sub(
145
- r"\[\s*([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)\s*\]",
169
+ r"\[\s*([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\s*\]",
146
170
  r"(\1)",
147
171
  expression,
148
172
  )
173
+
149
174
  return f"[{variable} = {expression}]"
150
175
 
151
- try:
152
- body = definition["body"]
176
+ def execute_function_lines(lines):
177
+ """Execute function statements and propagate return values."""
153
178
  i = 0
154
- while i < len(body):
155
- line = body[i].strip()
179
+
180
+ while i < len(lines):
181
+ line = lines[i].strip()
182
+
156
183
  if not line:
157
184
  i += 1
158
185
  continue
159
186
 
160
- # A function may contain a multiline [[[ Python block ]]].
187
+ # Multiline Python block.
161
188
  if line == "[[[":
162
189
  block = []
163
190
  i += 1
164
- while i < len(body) and body[i].strip() != "]]]":
165
- block.append(substitute_parameters(body[i]))
191
+
192
+ while i < len(lines) and lines[i].strip() != "]]]":
193
+ source_line = substitute_parameters(lines[i])
194
+ source_line = _expand_definitions(source_line)
195
+ block.append(source_line)
166
196
  i += 1
167
- if i >= len(body):
168
- raise SyntaxError(f"Missing `]]]` in function `{name}`")
197
+
198
+ if i >= len(lines):
199
+ raise SyntaxError(
200
+ f"Missing `]]]` in function `{name}`"
201
+ )
202
+
169
203
  evaluate_python("\n".join(block))
170
204
  i += 1
171
205
  continue
172
206
 
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)
207
+ # Recognize block headers generically, while excluding
208
+ # closing and branch markers from being treated as headers.
209
+ # generic_header = re.match(
210
+ # r"^\[\s*(?!endblock\b|else\b|elif\b)"
211
+ # r"[A-Za-z_]\w*\b.*\]\s*$",
212
+ # line,
213
+ # )
214
+
215
+ if is_block_header(line):
216
+ end = find_endblock(lines, i + 1)
217
+
177
218
  block_body = [
178
219
  normalize_let_line(substitute_parameters(source_line))
179
- for source_line in body[i + 1:end]
220
+ for source_line in lines[i + 1:end]
180
221
  ]
222
+
181
223
  code = line[1:-1].strip()
224
+
182
225
  if code.startswith("for "):
183
226
  execute_for_block(line, block_body)
227
+
184
228
  elif code.startswith("while "):
185
229
  execute_while_block(line, block_body)
230
+
186
231
  elif code.startswith("if "):
187
- execute_if_block(line, block_body)
232
+ # Expand parameters and definitions before evaluating.
233
+ condition = code[3:].strip()
234
+ condition = substitute_parameters(condition)
235
+ condition = _expand_definitions(condition)
236
+
237
+ # Find the top-level else branch.
238
+ else_index = next(
239
+ (
240
+ j
241
+ for j, source_line in enumerate(block_body)
242
+ if source_line.strip() == "[else]"
243
+ ),
244
+ None,
245
+ )
246
+
247
+ if evaluate_python(condition):
248
+ selected_body = (
249
+ block_body
250
+ if else_index is None
251
+ else block_body[:else_index]
252
+ )
253
+ else:
254
+ selected_body = (
255
+ []
256
+ if else_index is None
257
+ else block_body[else_index + 1:]
258
+ )
259
+
260
+ # Execute only the selected branch. A return exits
261
+ # this function immediately.
262
+ result = execute_function_lines(selected_body)
263
+
264
+ if result is not _NO_RETURN:
265
+ return result
266
+
188
267
  else:
189
- raise SyntaxError(f"Unsupported function block `{code}`")
268
+ raise SyntaxError(
269
+ f"Unsupported function block `{code}`"
270
+ )
271
+
190
272
  i = end + 1
191
273
  continue
192
274
 
193
275
  if line == "[endblock]":
194
- raise SyntaxError(f"Unexpected `[endblock]` in function `{name}`")
276
+ raise SyntaxError(
277
+ f"Unexpected `[endblock]` in function `{name}`"
278
+ )
279
+
280
+ # Handle return statements inside the function.
281
+ if re.match(r"^return\b", line):
282
+ expression = re.sub(r"^return\b", "", line, count=1).strip()
195
283
 
196
- if line.startswith("return"):
197
- expression = line[len("return"):].strip()
198
284
  if expression.startswith("[") and expression.endswith("]"):
199
- expression = expression[1:-1]
285
+ expression = expression[1:-1].strip()
286
+
200
287
  expression = substitute_parameters(expression)
288
+ expression = _expand_definitions(expression)
289
+
201
290
  return evaluate_python(expression) if expression else None
202
291
 
203
292
  normalized = normalize_let_line(line)
293
+
204
294
  if normalized != line:
205
- # The wrapper is ChemQL syntax; pass the assignment itself to Python.
206
295
  evaluate_python(normalized[1:-1])
296
+ elif line.startswith("[") and line.endswith("]"):
297
+ expression = line[1:-1].strip()
298
+ expression = substitute_parameters(expression)
299
+ expression = _expand_definitions(expression)
300
+ evaluate_python(expression)
207
301
  else:
208
- evaluate_python(substitute_parameters(line))
302
+ expression = substitute_parameters(line)
303
+ expression = _expand_definitions(expression)
304
+ evaluate_python(expression)
305
+
209
306
  i += 1
210
- return None
307
+
308
+ return _NO_RETURN
309
+
310
+ try:
311
+ result = execute_function_lines(definition["body"])
312
+ return None if result is _NO_RETURN else result
313
+
211
314
  finally:
212
- # Keep function-local assignments from leaking into the caller's namespace.
315
+ # Restore the caller's namespaces.
213
316
  python_globals.clear()
214
317
  python_globals.update(saved_globals)
318
+
215
319
  CHEMQL_VARIABLES.clear()
216
320
  CHEMQL_VARIABLES.update(saved_chemql_variables)
321
+
217
322
  if had_arguments:
218
323
  python_globals["arguments"] = saved_arguments
219
324
  else:
@@ -2416,15 +2521,30 @@ def find_endblock(lines, start):
2416
2521
  for i in range(start, len(lines)):
2417
2522
  stripped = lines[i].strip()
2418
2523
 
2419
- if is_python_block_header(stripped):
2420
- depth += 1
2421
-
2422
- elif stripped == "[endblock]":
2524
+ # Closing block.
2525
+ if stripped == "[endblock]":
2423
2526
  depth -= 1
2424
2527
 
2425
2528
  if depth == 0:
2426
2529
  return i
2427
2530
 
2531
+ continue
2532
+
2533
+ # Branch markers do not start new blocks.
2534
+ if stripped in ("[else]", "[endif]", "[endfor]", "[endwhile]"):
2535
+ continue
2536
+
2537
+ # Recognize both validated Python headers and generic
2538
+ # ChemQL block headers, consistently with _call_function().
2539
+ generic_header = re.match(
2540
+ r"^\[\s*(?!endblock\b|else\b|elif\b)"
2541
+ r"[A-Za-z_]\w*\b.*\]\s*$",
2542
+ stripped,
2543
+ )
2544
+
2545
+ if is_block_header(stripped):
2546
+ depth += 1
2547
+
2428
2548
  raise SyntaxError("Missing `[endblock]`")
2429
2549
 
2430
2550
  def remove_comments(line):
@@ -2593,7 +2713,7 @@ def execute_if_block(header, body):
2593
2713
  )
2594
2714
 
2595
2715
  for section_header, section_body in sections:
2596
- code = section_header[1:-1].strip()
2716
+ code = _expand_definitions(section_header[1:-1].strip())
2597
2717
 
2598
2718
  if code.startswith("if "):
2599
2719
  condition = code[3:].strip()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chemql-python
3
- Version: 1.2.2
3
+ Version: 1.2.4
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.2 combines database-style
20
+ elements, molecules, and reactions. Version 1.2.3 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.2\
28
- > The examples below describe the ChemQL 1.2.2 source. Dataset
27
+ > **Version covered by this README:** 1.2.3\
28
+ > The examples below describe the ChemQL 1.2.3 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.2 adds three ways to reuse values and logic. These declarations
541
+ ChemQL 1.2.3 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.2
836
+ chemql --upgrade 1.2.3
837
837
  ```
838
838
 
839
839
  `--upgrade` installs the latest published `chemql-python` release; supplying a
File without changes
File without changes