chemql-python 1.5.8__tar.gz → 1.5.10__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 (40) hide show
  1. {chemql_python-1.5.8 → chemql_python-1.5.10}/PKG-INFO +2 -2
  2. {chemql_python-1.5.8 → chemql_python-1.5.10}/README.md +1 -1
  3. {chemql_python-1.5.8 → chemql_python-1.5.10}/pyproject.toml +1 -1
  4. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/__init__.py +127 -4
  5. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql_python.egg-info/PKG-INFO +2 -2
  6. {chemql_python-1.5.8 → chemql_python-1.5.10}/LICENSE +0 -0
  7. {chemql_python-1.5.8 → chemql_python-1.5.10}/setup.cfg +0 -0
  8. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/__main__.py +0 -0
  9. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/codecql.py +0 -0
  10. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/colors.json +0 -0
  11. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/data/BondEnergies.json +0 -0
  12. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/data/MoleculeBonds.json +0 -0
  13. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/data/Molecules.json +0 -0
  14. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/data/PeriodicTableJSON.json +0 -0
  15. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/data/Reactions.json +0 -0
  16. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/data/THIRD_PARTY_LICENSES/PeriodicTableJSON.txt +0 -0
  17. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/data/THIRD_PARTY_LICENSES/RHEA.txt +0 -0
  18. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/html/periodic_table.html +0 -0
  19. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/stdlib/README.md +0 -0
  20. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/stdlib/collections.cql +0 -0
  21. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/stdlib/datetime.cql +0 -0
  22. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/stdlib/decimal.cql +0 -0
  23. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/stdlib/fractions.cql +0 -0
  24. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/stdlib/json.cql +0 -0
  25. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/stdlib/math/statistics.cql +0 -0
  26. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/stdlib/math.cql +0 -0
  27. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/stdlib/pathlib.cql +0 -0
  28. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/stdlib/random.cql +0 -0
  29. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql/stdlib/text.cql +0 -0
  30. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql_python.egg-info/SOURCES.txt +0 -0
  31. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql_python.egg-info/dependency_links.txt +0 -0
  32. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql_python.egg-info/entry_points.txt +0 -0
  33. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql_python.egg-info/requires.txt +0 -0
  34. {chemql_python-1.5.8 → chemql_python-1.5.10}/src/chemql_python.egg-info/top_level.txt +0 -0
  35. {chemql_python-1.5.8 → chemql_python-1.5.10}/tests/test_codecql.py +0 -0
  36. {chemql_python-1.5.8 → chemql_python-1.5.10}/tests/test_external_molecule_lookup.py +0 -0
  37. {chemql_python-1.5.8 → chemql_python-1.5.10}/tests/test_public_api.py +0 -0
  38. {chemql_python-1.5.8 → chemql_python-1.5.10}/tests/test_reactions.py +0 -0
  39. {chemql_python-1.5.8 → chemql_python-1.5.10}/tests/test_stdlib.py +0 -0
  40. {chemql_python-1.5.8 → chemql_python-1.5.10}/tests/test_structured_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chemql-python
3
- Version: 1.5.8
3
+ Version: 1.5.10
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
@@ -19,7 +19,7 @@ SQL-inspired syntax, inspect elements and molecules, look up reactions,
19
19
  balance equations, and combine chemistry queries with Python and shell
20
20
  commands.
21
21
 
22
- - **Version:** `1.5.8`
22
+ - **Version:** `1.5.10`
23
23
  - **PyPI distribution:** `chemql-python`
24
24
  - **Python import / CLI name:** `chemql`
25
25
  - **Author:** Mohammad Rakibul Islam
@@ -6,7 +6,7 @@ SQL-inspired syntax, inspect elements and molecules, look up reactions,
6
6
  balance equations, and combine chemistry queries with Python and shell
7
7
  commands.
8
8
 
9
- - **Version:** `1.5.8`
9
+ - **Version:** `1.5.10`
10
10
  - **PyPI distribution:** `chemql-python`
11
11
  - **Python import / CLI name:** `chemql`
12
12
  - **Author:** Mohammad Rakibul Islam
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "chemql-python"
7
- version = "1.5.8"
7
+ version = "1.5.10"
8
8
  authors = [
9
9
  { name = "Mohammad Rakibul Islam", email = "rakibulofficial1017@gmail.com" }
10
10
  ]
@@ -64,7 +64,7 @@ __all__ = [
64
64
  "__version__"
65
65
  ]
66
66
 
67
- __version__ = "1.5.8"
67
+ __version__ = "1.5.10"
68
68
 
69
69
  # Persistent preprocessor/function state for the current ChemQL session.
70
70
  DEFINITIONS = {}
@@ -3738,12 +3738,50 @@ def execute_query_block(text):
3738
3738
  return execute_query_text(text)
3739
3739
 
3740
3740
 
3741
+ def _run_python_in_active_namespace(compiled, mode):
3742
+ """Run compiled Python with the active ChemQL namespace's members visible.
3743
+
3744
+ Namespace members are temporarily overlaid on the shared Python globals so
3745
+ private helpers (for example ``_gui_add_button``) resolve inside functions
3746
+ belonging to their package, without permanently leaking those names into the
3747
+ caller's Python/ChemQL global scope.
3748
+ """
3749
+ namespace = _CURRENT_NAMESPACE
3750
+ if namespace is None:
3751
+ if mode == "eval":
3752
+ return eval(compiled, python_globals)
3753
+ exec(compiled, python_globals)
3754
+ return None
3755
+
3756
+ members = {}
3757
+ members.update(namespace._chemql_private)
3758
+ for member_name in namespace._chemql_public:
3759
+ if member_name in namespace.__dict__:
3760
+ members[member_name] = namespace.__dict__[member_name]
3761
+
3762
+ missing = object()
3763
+ previous = {name: python_globals.get(name, missing) for name in members}
3764
+ python_globals.update(members)
3765
+ try:
3766
+ if mode == "eval":
3767
+ return eval(compiled, python_globals)
3768
+ exec(compiled, python_globals)
3769
+ return None
3770
+ finally:
3771
+ for name, old_value in previous.items():
3772
+ if old_value is missing:
3773
+ python_globals.pop(name, None)
3774
+ else:
3775
+ python_globals[name] = old_value
3776
+
3777
+
3741
3778
  def evaluate_python(code):
3742
3779
  """Evaluate Python, with {{ query }} expressions available inside it.
3743
3780
 
3744
3781
  If the Python is an expression, its value is returned.
3745
3782
  Otherwise it is executed and None is returned.
3746
3783
  """
3784
+ code = code.strip()
3747
3785
  query_values = {}
3748
3786
  rewritten = []
3749
3787
  i = 0
@@ -3785,10 +3823,13 @@ def evaluate_python(code):
3785
3823
  if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store):
3786
3824
  if isinstance(node.ctx, ast.Store) and node.id.isidentifier() and not node.id.startswith("__chemql_"):
3787
3825
  CHEMQL_VARIABLES.add(node.id)
3788
- exec(compile(module, "<embedded-python>", "exec"), python_globals)
3789
- return None
3826
+ return _run_python_in_active_namespace(
3827
+ compile(module, "<embedded-python>", "exec"), "exec"
3828
+ )
3790
3829
 
3791
- return eval(compile(tree, "<embedded-python>", "eval"), python_globals)
3830
+ return _run_python_in_active_namespace(
3831
+ compile(tree, "<embedded-python>", "eval"), "eval"
3832
+ )
3792
3833
 
3793
3834
 
3794
3835
  def is_python_block_header(text):
@@ -4176,6 +4217,16 @@ def process_inline(text):
4176
4217
  if not stripped:
4177
4218
  return
4178
4219
 
4220
+ # A standalone quoted string is a Python literal, not a ChemQL command.
4221
+ # Check only complete literals so quotes inside queries remain untouched.
4222
+ try:
4223
+ literal_value = ast.literal_eval(stripped)
4224
+ except (SyntaxError, ValueError, TypeError):
4225
+ literal_value = None
4226
+ else:
4227
+ if isinstance(literal_value, str):
4228
+ return literal_value
4229
+
4179
4230
  # Native dynamically typed variables: `let name = expression`.
4180
4231
  let_match = re.fullmatch(r"let\s+([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)\s*=\s*(.+)", stripped, re.DOTALL)
4181
4232
  if let_match:
@@ -4438,10 +4489,32 @@ def _ensure_package_namespace(package_name, allow_namespace_only=False):
4438
4489
  if path is not None and CHEMQL_PACKAGE_BINDINGS.get(package_name) != package_name:
4439
4490
  source_lines = path.read_text(encoding="utf-8").splitlines()
4440
4491
  old_namespace, old_visibility = _CURRENT_NAMESPACE, _CURRENT_VISIBILITY
4492
+ # A package's Python blocks execute in the shared Python globals. Keep
4493
+ # a snapshot so names created by any supported Python syntax (single-line
4494
+ # blocks, multiline blocks, imports, assignments, defs, and classes) can
4495
+ # be attached to the package namespace after its source has executed.
4496
+ globals_before_package = python_globals.copy()
4441
4497
  try:
4442
4498
  globals()["_CURRENT_NAMESPACE"] = namespace
4443
4499
  globals()["_CURRENT_VISIBILITY"] = "private"
4444
4500
  process_lines(source_lines)
4501
+
4502
+ # Capture Python names introduced or replaced by this package. Do
4503
+ # not overwrite explicit ChemQL members, and do not capture runtime
4504
+ # internals or temporary values used by embedded-query expansion.
4505
+ existing_members = (
4506
+ set(namespace._chemql_public)
4507
+ | set(namespace._chemql_private)
4508
+ )
4509
+ ignored_prefixes = ("__chemql_",)
4510
+ for member_name, member_value in list(python_globals.items()):
4511
+ if member_name.startswith(ignored_prefixes):
4512
+ continue
4513
+ if member_name in {"__builtins__", "math", "error"}:
4514
+ continue
4515
+ if member_name not in globals_before_package or globals_before_package.get(member_name) is not member_value:
4516
+ if member_name not in existing_members:
4517
+ namespace._set_member(member_name, member_value, False)
4445
4518
  except Exception:
4446
4519
  # Do not mark a partially executed package as successfully loaded.
4447
4520
  NAMESPACES.pop(package_name, None)
@@ -4598,6 +4671,12 @@ def process_lines(lines):
4598
4671
  if re.match(r"^(for|while|if)\s+.+$", stripped_source) and not stripped_source.startswith("["):
4599
4672
  # Existing block parser supplies the trailing colon to Python's AST.
4600
4673
  source_line = "[" + stripped_source + "]"
4674
+ elif re.match(r"^elif\s+.+$", stripped_source, re.IGNORECASE):
4675
+ # Normalize branch markers so execute_if_block() can recognize
4676
+ # them as part of the current conditional chain.
4677
+ source_line = "[" + stripped_source + "]"
4678
+ elif stripped_source.casefold() == "else":
4679
+ source_line = "[else]"
4601
4680
  elif stripped_source.casefold() in {"endfor", "endwhile", "endif"}:
4602
4681
  source_line = "[endblock]"
4603
4682
  normalized_lines.append(source_line)
@@ -5229,6 +5308,50 @@ def process_lines(lines):
5229
5308
  try:
5230
5309
  with contextlib.redirect_stdout(captured):
5231
5310
  value = evaluate_python(code)
5311
+
5312
+ # Python names declared inside a package's Python block must
5313
+ # also belong to that package's private namespace. Otherwise
5314
+ # a ChemQL function such as `GUI::addButton` cannot resolve
5315
+ # `_gui_add_button` when it executes in the package context.
5316
+ if _CURRENT_NAMESPACE is not None:
5317
+ try:
5318
+ python_module = ast.parse(code, mode="exec")
5319
+ declared_names = set()
5320
+
5321
+ def collect_target_names(target):
5322
+ if isinstance(target, ast.Name):
5323
+ declared_names.add(target.id)
5324
+ elif isinstance(target, (ast.Tuple, ast.List)):
5325
+ for element in target.elts:
5326
+ collect_target_names(element)
5327
+
5328
+ for node in python_module.body:
5329
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
5330
+ declared_names.add(node.name)
5331
+ elif isinstance(node, ast.Import):
5332
+ for alias in node.names:
5333
+ declared_names.add(alias.asname or alias.name.split(".")[0])
5334
+ elif isinstance(node, ast.ImportFrom):
5335
+ for alias in node.names:
5336
+ if alias.name != "*":
5337
+ declared_names.add(alias.asname or alias.name)
5338
+ elif isinstance(node, (ast.Assign, ast.AnnAssign, ast.AugAssign)):
5339
+ targets = node.targets if isinstance(node, ast.Assign) else [node.target]
5340
+ for target in targets:
5341
+ collect_target_names(target)
5342
+
5343
+ for declared_name in declared_names:
5344
+ if declared_name in python_globals:
5345
+ _CURRENT_NAMESPACE._set_member(
5346
+ declared_name,
5347
+ python_globals[declared_name],
5348
+ _CURRENT_VISIBILITY == "public",
5349
+ )
5350
+ except (SyntaxError, AttributeError):
5351
+ # The Python block already executed successfully; name
5352
+ # capture is best-effort and must not mask its result.
5353
+ pass
5354
+
5232
5355
  output = captured.getvalue()
5233
5356
  if value is not None:
5234
5357
  output += str(value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chemql-python
3
- Version: 1.5.8
3
+ Version: 1.5.10
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
@@ -19,7 +19,7 @@ SQL-inspired syntax, inspect elements and molecules, look up reactions,
19
19
  balance equations, and combine chemistry queries with Python and shell
20
20
  commands.
21
21
 
22
- - **Version:** `1.5.8`
22
+ - **Version:** `1.5.10`
23
23
  - **PyPI distribution:** `chemql-python`
24
24
  - **Python import / CLI name:** `chemql`
25
25
  - **Author:** Mohammad Rakibul Islam
File without changes
File without changes