chemql-python 1.3.1__tar.gz → 1.3.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.3.1/src/chemql_python.egg-info → chemql_python-1.3.2}/PKG-INFO +2 -2
  2. {chemql_python-1.3.1 → chemql_python-1.3.2}/README.md +1 -1
  3. {chemql_python-1.3.1 → chemql_python-1.3.2}/pyproject.toml +1 -1
  4. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/__init__.py +70 -68
  5. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/__main__.py +1 -1
  6. {chemql_python-1.3.1 → chemql_python-1.3.2/src/chemql_python.egg-info}/PKG-INFO +2 -2
  7. {chemql_python-1.3.1 → chemql_python-1.3.2}/LICENSE +0 -0
  8. {chemql_python-1.3.1 → chemql_python-1.3.2}/setup.cfg +0 -0
  9. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/data/BondEnergies.json +0 -0
  10. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/data/MoleculeBonds.json +0 -0
  11. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/data/Molecules.json +0 -0
  12. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/data/PeriodicTableJSON.json +0 -0
  13. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/data/Reactions.json +0 -0
  14. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/data/THIRD_PARTY_LICENSES/PeriodicTableJSON.txt +0 -0
  15. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/data/THIRD_PARTY_LICENSES/RHEA.txt +0 -0
  16. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/html/periodic_table.html +0 -0
  17. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql_python.egg-info/SOURCES.txt +0 -0
  18. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql_python.egg-info/dependency_links.txt +0 -0
  19. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql_python.egg-info/entry_points.txt +0 -0
  20. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql_python.egg-info/requires.txt +0 -0
  21. {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql_python.egg-info/top_level.txt +0 -0
  22. {chemql_python-1.3.1 → chemql_python-1.3.2}/tests/test_external_molecule_lookup.py +0 -0
  23. {chemql_python-1.3.1 → chemql_python-1.3.2}/tests/test_public_api.py +0 -0
  24. {chemql_python-1.3.1 → chemql_python-1.3.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.3.1
3
+ Version: 1.3.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
@@ -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.3.1`
22
+ - **Version:** `1.3.2`
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.3.1`
9
+ - **Version:** `1.3.2`
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.3.1"
7
+ version = "1.3.2"
8
8
  authors = [
9
9
  { name = "Mohammad Rakibul Islam", email = "rakibulofficial1017@gmail.com" }
10
10
  ]
@@ -52,7 +52,7 @@ __all__ = [
52
52
  "__version__"
53
53
  ]
54
54
 
55
- __version__ = "1.3.1"
55
+ __version__ = "1.3.2"
56
56
 
57
57
  # Persistent preprocessor/function state for the current ChemQL session.
58
58
  DEFINITIONS = {}
@@ -61,6 +61,7 @@ MACROS = {}
61
61
  EXECUTION_HISTORY = []
62
62
  _DEFINITION_NAME = r"[A-Za-z_][A-Za-z0-9_]*"
63
63
  _NO_RETURN = object()
64
+ NO_ANSI = True
64
65
 
65
66
  def _expand_definitions(line):
66
67
  """Replace @NAME literally, including inside strings, using current definitions."""
@@ -993,10 +994,11 @@ _CHEMQL_RISK_DEPTH = 0
993
994
 
994
995
 
995
996
  def format_error(message, text=None, position=None, length=1):
996
- output = f"Error: {message}"
997
+ output = ""
998
+ if (text is not None) and (position is not None) and (not NO_ANSI):
999
+ output += f"\033[1;31m{' ' * (position+4)}{'^' * max(1, length)}\n\033[0m"
1000
+ output += f"Error: {message}" if NO_ANSI else f"\033[31mError: {message}\033[0m"
997
1001
 
998
- if text is not None and position is not None:
999
- output += f"\n{text}\n{' ' * position}{'^' * max(1, length)}"
1000
1002
 
1001
1003
  return output
1002
1004
 
@@ -2293,40 +2295,40 @@ def _formula_counts(formula):
2293
2295
  if char == "(":
2294
2296
  group, index = parse_group(index + 1, True)
2295
2297
  if index >= len(formula) or formula[index] != ")":
2296
- raise ValueError(f"Unclosed parenthesis in formula `{formula}`")
2298
+ raise ValueError((f"Unclosed parenthesis in formula `{formula}`" if NO_ANSI else f"Unclosed parenthesis in formula \033[1m{formula}\033[22m"))
2297
2299
  index += 1
2298
2300
  match = re.match(r"\d+", formula[index:])
2299
2301
  multiplier = int(match.group()) if match else 1
2300
2302
  if multiplier <= 0:
2301
- raise ValueError(f"Invalid atom count in formula `{formula}`")
2303
+ raise ValueError((f"Invalid atom count in formula `{formula}`" if NO_ANSI else f"Invalid atom count in formula \033[1m{formula}\033[22m"))
2302
2304
  if match:
2303
2305
  index += len(match.group())
2304
2306
  for symbol, amount in group.items():
2305
2307
  counts[symbol] = counts.get(symbol, 0) + amount * multiplier
2306
2308
  continue
2307
2309
  if not char.isupper():
2308
- raise ValueError(f"Invalid chemical formula `{formula}`")
2310
+ raise ValueError((f"Invalid chemical formula `{formula}`" if NO_ANSI else f"Invalid chemical formula \033[1m{formula}\033[22m"))
2309
2311
  end = index + 1
2310
2312
  if end < len(formula) and formula[end].islower():
2311
2313
  end += 1
2312
2314
  symbol = formula[index:end]
2313
2315
  if symbol not in symbols:
2314
- raise ValueError(f"Unknown element `{symbol}` in formula `{formula}`")
2316
+ raise ValueError((f"Unknown element `{symbol}` in formula `{formula}`" if NO_ANSI else f"Unknown element `{symbol}` in formula \033[1m{formula}\033[22m"))
2315
2317
  index = end
2316
2318
  match = re.match(r"\d+", formula[index:])
2317
2319
  amount = int(match.group()) if match else 1
2318
2320
  if amount <= 0:
2319
- raise ValueError(f"Invalid atom count in formula `{formula}`")
2321
+ raise ValueError((f"Invalid atom count in formula `{formula}`" if NO_ANSI else f"Invalid atom count in formula \033[1m{formula}\033[22m"))
2320
2322
  if match:
2321
2323
  index += len(match.group())
2322
2324
  counts[symbol] = counts.get(symbol, 0) + amount
2323
2325
  if nested:
2324
- raise ValueError(f"Unclosed parenthesis in formula `{formula}`")
2326
+ raise ValueError((f"Unclosed parenthesis in formula `{formula}`" if NO_ANSI else f"Unclosed parenthesis in formula \033[1m{formula}\033[22m"))
2325
2327
  return counts, index
2326
2328
 
2327
2329
  counts, end = parse_group(0)
2328
2330
  if end != len(formula) or not counts:
2329
- raise ValueError(f"Invalid chemical formula `{formula}`")
2331
+ raise ValueError((f"Invalid chemical formula `{formula}`" if NO_ANSI else f"Invalid chemical formula \033[1m{formula}\033[22m"))
2330
2332
  return counts
2331
2333
 
2332
2334
 
@@ -2455,7 +2457,7 @@ def queryElement(key, func, value, *params):
2455
2457
 
2456
2458
  def queryElementLast(key, func, value, *params):
2457
2459
  if key not in elementKeys:
2458
- raise KeyError(f"There is no key `{key}`")
2460
+ raise KeyError((f"There is no key `{key}`" if NO_ANSI else f"There is no key \033[1m{key}\033[22m"))
2459
2461
 
2460
2462
  for element in reversed(ELEMENTS):
2461
2463
  if getattr(element[key], func)(value, *params):
@@ -2466,7 +2468,7 @@ def queryElementLast(key, func, value, *params):
2466
2468
 
2467
2469
  def queryElementAll(key, func, value, *params):
2468
2470
  if key not in elementKeys:
2469
- raise KeyError(f"There is no key `{key}`")
2471
+ raise KeyError((f"There is no key `{key}`" if NO_ANSI else f"There is no key \033[1m{key}\033[22m"))
2470
2472
 
2471
2473
  found = False
2472
2474
 
@@ -2527,7 +2529,7 @@ def sort_results(results, key, descending=False):
2527
2529
  reverse=descending
2528
2530
  )
2529
2531
  except (TypeError, AttributeError):
2530
- raise ValueError(f"Cannot sort by `{key}`")
2532
+ raise ValueError((f"Cannot sort by `{key}`" if NO_ANSI else f"Cannot sort by \033[1m{key}\033[22m"))
2531
2533
 
2532
2534
  def apply_query_operations(results, tokens, keys, positions=None):
2533
2535
  i = 0
@@ -2538,13 +2540,13 @@ def apply_query_operations(results, tokens, keys, positions=None):
2538
2540
  # sort
2539
2541
  if token == "sort":
2540
2542
  if i + 1 >= len(tokens):
2541
- raise ChemQLError("Expected a key after `sort`", *(error_position(positions, i) if positions else (None, 1))) #type:ignore
2543
+ raise ChemQLError(("Expected a key after `sort`" if NO_ANSI else "Expected a key after \033[1msort\033[22m"), *(error_position(positions, i) if positions else (None, 1))) #type:ignore
2542
2544
 
2543
2545
  key = tokens[i + 1]
2544
2546
 
2545
2547
  if key not in keys:
2546
2548
  pos, length = error_position(positions, i + 1) if positions else (None, 1)
2547
- raise ChemQLError(f"There is no key `{key}`", pos, length)
2549
+ raise ChemQLError((f"There is no key `{key}`" if NO_ANSI else f"There is no key \033[1m{key}\033[22m"), pos, length)
2548
2550
 
2549
2551
  descending = False
2550
2552
 
@@ -2573,17 +2575,17 @@ def apply_query_operations(results, tokens, keys, positions=None):
2573
2575
  # limit
2574
2576
  elif token == "limit":
2575
2577
  if i + 1 >= len(tokens):
2576
- raise ChemQLError("Expected a number after `limit`", *(error_position(positions, i) if positions else (None, 1)))#type:ignore
2578
+ raise ChemQLError(("Expected a number after `limit`" if NO_ANSI else "Expected a number after \033[1mlimit\033[22m"), *(error_position(positions, i) if positions else (None, 1)))#type:ignore
2577
2579
 
2578
2580
  try:
2579
2581
  amount = int(tokens[i + 1])
2580
2582
  except ValueError:
2581
2583
  pos, length = error_position(positions, i + 1) if positions else (None, 1)
2582
- raise ChemQLError("`limit` requires an integer", pos, length)
2584
+ raise ChemQLError(("`limit` requires an integer" if NO_ANSI else "\033[1mlimit\033[22m requires an integer"), pos, length)
2583
2585
 
2584
2586
  if amount < 0:
2585
2587
  pos, length = error_position(positions, i + 1) if positions else (None, 1)
2586
- raise ChemQLError("`limit` cannot be negative", pos, length)
2588
+ raise ChemQLError(("`limit` cannot be negative" if NO_ANSI else "\033[1mlimit\033[22m cannot be negative"), pos, length)
2587
2589
 
2588
2590
  results = results[:amount] # type: ignore
2589
2591
 
@@ -2593,14 +2595,14 @@ def apply_query_operations(results, tokens, keys, positions=None):
2593
2595
  elif token == "count":
2594
2596
  if i + 1 < len(tokens):
2595
2597
  pos, length = error_position(positions, i) if positions else (None, 1)
2596
- raise ChemQLError("`count` must be the final query operation", pos, length)
2598
+ raise ChemQLError(("`count` must be the final query operation" if NO_ANSI else "\033[1mcount\033[22m must be the final query operation"), pos, length)
2597
2599
 
2598
2600
  results = len(results) # type: ignore
2599
2601
  i += 1
2600
2602
 
2601
2603
  else:
2602
2604
  pos, length = error_position(positions, i) if positions else (None, 1)
2603
- raise ChemQLError(f"Unknown query operation `{token}`", pos, length)
2605
+ raise ChemQLError((f"Unknown query operation `{token}`" if NO_ANSI else f"Unknown query operation \033[1m{token}\033[22m"), pos, length)
2604
2606
 
2605
2607
  return results
2606
2608
 
@@ -2619,7 +2621,7 @@ def convert_value(value, sample):
2619
2621
  if value.lower() == "false":
2620
2622
  return False
2621
2623
 
2622
- raise ValueError(f"`{value}` is not a boolean")
2624
+ raise ValueError((f"`{value}` is not a boolean" if NO_ANSI else f"\033[1m{value}\033[22m is not a boolean"))
2623
2625
 
2624
2626
  if isinstance(sample, (int, float)):
2625
2627
 
@@ -2632,7 +2634,7 @@ def convert_value(value, sample):
2632
2634
  return number
2633
2635
 
2634
2636
  except ValueError:
2635
- raise ValueError(f"`{value}` is not a number")
2637
+ raise ValueError((f"`{value}` is not a number" if NO_ANSI else f"\033[1m{value}\033[22m is not a number"))
2636
2638
 
2637
2639
  return value
2638
2640
 
@@ -2652,7 +2654,7 @@ def parse_condition(tokens, index, source, keys, positions=None):
2652
2654
  negated = operator.casefold() == "not"
2653
2655
  if negated:
2654
2656
  if index + 3 >= len(tokens):
2655
- raise ChemQLError("Expected an operator and value after `not`", *(error_position(positions, index + 1) if positions else (None, 1))) # type: ignore
2657
+ raise ChemQLError(("Expected an operator and value after `not`" if NO_ANSI else "Expected an operator and value after \033[1mnot\033[22m"), *(error_position(positions, index + 1) if positions else (None, 1))) # type: ignore
2656
2658
  operator = tokens[index + 2]
2657
2659
  value = tokens[index + 3]
2658
2660
  consumed = index + 4
@@ -2662,7 +2664,7 @@ def parse_condition(tokens, index, source, keys, positions=None):
2662
2664
 
2663
2665
  if key not in keys:
2664
2666
  pos, length = error_position(positions, index) if positions else (None, 1)
2665
- raise ChemQLError(f"There is no key `{key}`", pos, length)
2667
+ raise ChemQLError((f"There is no key `{key}`" if NO_ANSI else f"There is no key \033[1m{key}\033[22m"), pos, length)
2666
2668
 
2667
2669
  if operator in {"like", "like!", "likep"}:
2668
2670
  pattern = value
@@ -2737,7 +2739,7 @@ def parse_condition(tokens, index, source, keys, positions=None):
2737
2739
 
2738
2740
  if operator not in functionMap:
2739
2741
  pos, length = error_position(positions, index + 1) if positions else (None, 1)
2740
- raise ChemQLError(f"Unknown operator `{operator}`", pos, length)
2742
+ raise ChemQLError((f"Unknown operator `{operator}`" if NO_ANSI else f"Unknown operator \033[1m{operator}\033[22m"), pos, length)
2741
2743
 
2742
2744
  sample = getattr(source[0], key)
2743
2745
 
@@ -2779,7 +2781,7 @@ def parse_primary(tokens, index, source, keys, positions=None):
2779
2781
 
2780
2782
  if index >= len(tokens) or tokens[index] != ")":
2781
2783
  pos = positions[index][0] if positions and index < len(positions) else None
2782
- raise ChemQLError("Missing `)`", pos)
2784
+ raise ChemQLError(("Missing `)`" if NO_ANSI else "Missing \033[1m)\033[22m"), pos)
2783
2785
 
2784
2786
  return expression, index + 1
2785
2787
 
@@ -2867,7 +2869,7 @@ def parse_query(tokens, source, keys, positions=None):
2867
2869
  if index < len(tokens) and tokens[index] not in queryOperations:
2868
2870
  pos, length = error_position(positions, index) if positions else (None, 1)
2869
2871
  raise ChemQLError(
2870
- f"Unexpected `{tokens[index]}`", pos, length
2872
+ (f"Unexpected `{tokens[index]}`" if NO_ANSI else f"Unexpected \033[1m{tokens[index]}\033[22m"), pos, length
2871
2873
  )
2872
2874
 
2873
2875
  return condition, index
@@ -2950,7 +2952,7 @@ def find_matching(text, start, opening, closing):
2950
2952
 
2951
2953
  i += 1
2952
2954
 
2953
- raise SyntaxError(f"Missing `{closing}`")
2955
+ raise SyntaxError((f"Missing `{closing}`" if NO_ANSI else f"Missing \033[1m{closing}\033[22m"))
2954
2956
 
2955
2957
 
2956
2958
  def interpolate_query(text):
@@ -3273,7 +3275,7 @@ def find_endblock(lines, start):
3273
3275
  # expand to a ChemQL struct representation that is not Python syntax.
3274
3276
  if re.match(r"^\[\s*(?:if|for|while)\b", stripped):
3275
3277
  depth += 1
3276
- raise SyntaxError("Missing block terminator (`endblock`, `endfor`, `endwhile`, or `endif`)")
3278
+ raise SyntaxError(("Missing block terminator (`endblock`, `endfor`, `endwhile`, or `endif`)" if NO_ANSI else "Missing block terminator (\033[1mendblock\033[22m, \033[1mendfor\033[22m, \033[1mendwhile\033[22m, or \033[1mendif\033[22m)"))
3277
3279
 
3278
3280
 
3279
3281
  def remove_comments(line):
@@ -3319,7 +3321,7 @@ def execute_for_block(header, body):
3319
3321
  node = tree.body[0]
3320
3322
 
3321
3323
  if not isinstance(node, ast.For):
3322
- raise SyntaxError("Expected a `for` block")
3324
+ raise SyntaxError(("Expected a `for` block" if NO_ANSI else "Expected a \033[1mfor\033[22m block"))
3323
3325
 
3324
3326
  iterable = eval(
3325
3327
  compile(ast.Expression(node.iter), "<embedded-python>", "eval"),
@@ -3378,7 +3380,7 @@ def execute_while_block(header, body):
3378
3380
  node = tree.body[0]
3379
3381
 
3380
3382
  if not isinstance(node, ast.While):
3381
- raise SyntaxError("Expected a `while` block")
3383
+ raise SyntaxError(("Expected a `while` block" if NO_ANSI else "Expected a \033[1mwhile\033[22m block"))
3382
3384
 
3383
3385
  while eval(
3384
3386
  compile(
@@ -3456,7 +3458,7 @@ def execute_if_block(header, body):
3456
3458
 
3457
3459
  else:
3458
3460
  raise SyntaxError(
3459
- f"Invalid conditional `{code}`"
3461
+ (f"Invalid conditional `{code}`" if NO_ANSI else f"Invalid conditional \033[1m{code}\033[22m")
3460
3462
  )
3461
3463
 
3462
3464
  if evaluate_python(condition):
@@ -3801,7 +3803,7 @@ def process_lines(lines):
3801
3803
  while end < len(lines) and lines[end].strip().casefold() != terminator:
3802
3804
  end += 1
3803
3805
  if end >= len(lines):
3804
- raise SyntaxError(f"Missing `{terminator}` for {kind} `{type_name}`")
3806
+ raise SyntaxError((f"Missing `{terminator}` for {kind} `{type_name}`" if NO_ANSI else f"Missing \033[1m{terminator}\033[22m for {kind} \033[1m{type_name}\033[22m"))
3805
3807
  fields, defaults = [], {}
3806
3808
  for field_line in lines[i + 1:end]:
3807
3809
  field_line = remove_comments(field_line).strip()
@@ -3812,12 +3814,12 @@ def process_lines(lines):
3812
3814
  raise SyntaxError(f"Invalid {kind} field declaration: {field_line}")
3813
3815
  field, default = match.groups()
3814
3816
  if field in fields:
3815
- raise SyntaxError(f"Duplicate field `{field}` in {type_name}")
3817
+ raise SyntaxError((f"Duplicate field `{field}` in {type_name}" if NO_ANSI else f"Duplicate field \033[1m{field}\033[22m in {type_name}"))
3816
3818
  fields.append(field)
3817
3819
  if default is not None:
3818
3820
  defaults[field] = evaluate_python(_expand_definitions(default))
3819
3821
  if not fields:
3820
- raise SyntaxError(f"{kind.title()} `{type_name}` must declare fields")
3822
+ raise SyntaxError((f"{kind.title()} `{type_name}` must declare fields" if NO_ANSI else f"{kind.title()} \033[1m{type_name}\033[22m must declare fields"))
3821
3823
  if kind == "struct":
3822
3824
  def make_struct(class_name, field_names, field_defaults):
3823
3825
  def __init__(self, *args, **kwargs):
@@ -3826,7 +3828,7 @@ def process_lines(lines):
3826
3828
  values = dict(zip(field_names, args))
3827
3829
  overlap = values.keys() & kwargs.keys()
3828
3830
  if overlap:
3829
- raise TypeError(f"Multiple values for field `{next(iter(overlap))}`")
3831
+ raise TypeError((f"Multiple values for field `{next(iter(overlap))}`" if NO_ANSI else f"Multiple values for field \033[1m{next(iter(overlap))}\033[22m"))
3830
3832
  values.update(kwargs)
3831
3833
  extra = values.keys() - set(field_names)
3832
3834
  if extra:
@@ -3844,7 +3846,7 @@ def process_lines(lines):
3844
3846
  from collections import namedtuple
3845
3847
  first_default = next((n for n, f in enumerate(fields) if f in defaults), len(fields))
3846
3848
  if any(f not in defaults for f in fields[first_default:]):
3847
- raise SyntaxError(f"Record `{type_name}` fields with defaults must be trailing fields")
3849
+ raise SyntaxError((f"Record `{type_name}` fields with defaults must be trailing fields" if NO_ANSI else f"Record \033[1m{type_name}\033[22m fields with defaults must be trailing fields"))
3848
3850
  generated = namedtuple(type_name, fields, defaults=[defaults[f] for f in fields[first_default:]])
3849
3851
  generated.__str__ = lambda self, _name=type_name: _name + " -> " + ", ".join(f"{f}: {getattr(self, f)}" for f in self._fields)
3850
3852
  RECORDS[type_name] = generated
@@ -3863,7 +3865,7 @@ def process_lines(lines):
3863
3865
  if nesting == 0: break
3864
3866
  end += 1
3865
3867
  if end >= len(lines):
3866
- raise SyntaxError("Missing `endrisk`")
3868
+ raise SyntaxError(("Missing `endrisk`" if NO_ANSI else "Missing \033[1mendrisk\033[22m"))
3867
3869
  sections = {"risk": [], "fix": [], "cleanup": []}
3868
3870
  section = "risk"
3869
3871
  for risk_line in lines[i + 1:end]:
@@ -3935,13 +3937,13 @@ def process_lines(lines):
3935
3937
  while end < len(lines) and lines[end].strip().casefold() != "endfunc":
3936
3938
  end += 1
3937
3939
  if end >= len(lines):
3938
- raise SyntaxError(f"Missing `endfunc` for function `{name}`")
3940
+ raise SyntaxError((f"Missing `endfunc` for function `{name}`" if NO_ANSI else f"Missing `endfunc` for function \033[1m{name}\033[22m"))
3939
3941
  FUNCTIONS[name] = {"body": [_expand_definitions(source_line) for source_line in lines[i + 1:end]]}
3940
3942
  if "." in name:
3941
3943
  owner_name, method_name = name.split(".", 1)
3942
3944
  owner = STRUCTS.get(owner_name)
3943
3945
  if owner is None:
3944
- raise NameError(f"Cannot define method `{name}`: struct `{owner_name}` does not exist")
3946
+ raise NameError((f"Cannot define method `{name}`: struct `{owner_name}` does not exist" if NO_ANSI else f"Cannot define method `{name}`: struct \033[1m{owner_name}\033[22m does not exist"))
3945
3947
  def _make_struct_method(function_name):
3946
3948
  def _method(instance, *args):
3947
3949
  had_this = "this" in python_globals
@@ -3978,7 +3980,7 @@ def process_lines(lines):
3978
3980
  while end < len(lines) and lines[end].strip().casefold() != "enddefine":
3979
3981
  end += 1
3980
3982
  if end >= len(lines):
3981
- raise SyntaxError(f"Missing `enddefine` for macro `{name}`")
3983
+ raise SyntaxError((f"Missing `enddefine` for macro `{name}`" if NO_ANSI else f"Missing `enddefine` for macro \033[1m{name}\033[22m"))
3982
3984
  MACROS[name] = [_expand_definitions(source_line) for source_line in lines[i + 1:end]]
3983
3985
  i = end + 1
3984
3986
  continue
@@ -4006,7 +4008,7 @@ def process_lines(lines):
4006
4008
  break
4007
4009
  close_line += 1
4008
4010
  if close_line >= len(lines):
4009
- raise SyntaxError("Missing closing `///` for multiline comment")
4011
+ raise SyntaxError(("Missing closing `///` for multiline comment" if NO_ANSI else "Missing closing \033[1m///\033[22m for multiline comment"))
4010
4012
  suffix = lines[close_line][close_pos + 3:]
4011
4013
  lines[i] = prefix
4012
4014
  del lines[i + 1:close_line + 1]
@@ -4030,7 +4032,7 @@ def process_lines(lines):
4030
4032
  body.append(lines[i])
4031
4033
  i += 1
4032
4034
  if i >= len(lines):
4033
- raise SyntaxError("Missing closing `]]]` for Python block")
4035
+ raise SyntaxError(("Missing closing `]]]` for Python block" if NO_ANSI else "Missing closing \033[1m]]]\033[22m for Python block"))
4034
4036
  body = [_expand_definitions(source_line) for source_line in body]
4035
4037
  code = "\n".join(body)
4036
4038
  captured = io.StringIO()
@@ -4059,7 +4061,7 @@ def process_lines(lines):
4059
4061
  body.append(lines[i])
4060
4062
  i += 1
4061
4063
  if i >= len(lines):
4062
- raise SyntaxError("Missing closing ``` for terminal block")
4064
+ raise SyntaxError("Missing closing \033[1m```\033[22m for terminal block")
4063
4065
  body = [_expand_definitions(source_line) for source_line in body]
4064
4066
  command = "\n".join(body)
4065
4067
  result = run_shell_command(command)
@@ -4082,7 +4084,7 @@ def process_lines(lines):
4082
4084
  continue
4083
4085
 
4084
4086
  if line == "[endblock]":
4085
- raise SyntaxError("Unexpected `[endblock]`")
4087
+ raise SyntaxError(("Unexpected `[endblock]`" if NO_ANSI else "Unexpected \033[1m[endblock]\033[22m"))
4086
4088
 
4087
4089
  # Existing Python control-flow blocks.
4088
4090
  if is_python_block_header(line):
@@ -4096,7 +4098,7 @@ def process_lines(lines):
4096
4098
  elif code.startswith("if "):
4097
4099
  execute_if_block(line, body)
4098
4100
  else:
4099
- raise SyntaxError(f"Unsupported block `{code}`")
4101
+ raise SyntaxError((f"Unsupported block `{code}`" if NO_ANSI else f"Unsupported block \033[1m{code}\033[22m"))
4100
4102
  i = end + 1
4101
4103
  continue
4102
4104
 
@@ -4194,7 +4196,7 @@ def parse_temperature_setting(value):
4194
4196
 
4195
4197
  match = re.fullmatch(r"([+-]?(?:\d+(?:\.\d*)?|\.\d+))\s*(?:°\s*)?(celsius|fahrenheit|kelvin|degc|degf|degk|c|f|k)", normalized)
4196
4198
  if not match:
4197
- raise ValueError("Temperature must use C, F, or K, or be `standard` or `room`")
4199
+ raise ValueError(("Temperature must use C, F, or K, or be `standard` or `room`" if NO_ANSI else "Temperature must use C, F, or K, or be \033[1mstandard\033[22m or \033[1mroom\033[22m"))
4198
4200
 
4199
4201
  temperature = float(match.group(1))
4200
4202
  unit = match.group(2)
@@ -4362,7 +4364,7 @@ def react(values):
4362
4364
  molecule = _resolve_reaction_product(product)
4363
4365
  if molecule is None:
4364
4366
  label = product.get("name", product["molecule"])
4365
- return f"Could not resolve product molecule `{label}` for reaction `{reaction.id}`"
4367
+ return (f"Could not resolve product molecule `{label}` for reaction `{reaction.id}`" if NO_ANSI else f"Could not resolve product molecule \033[1m{label}\033[22m for reaction \033[1m{reaction.id}\033[22m")
4366
4368
  resolved.append((molecule, product["stoichiometric_coefficient"], product.get("phase")))
4367
4369
 
4368
4370
  if len(resolved) == 1:
@@ -4465,7 +4467,7 @@ def execute(arguments, query_text=None, positions=None):
4465
4467
  return "Catalysts cleared"
4466
4468
  return f"Catalysts set to {', '.join(reaction_state['catalysts'])}"
4467
4469
 
4468
- return f"Unknown reaction setting `{setting}`"
4470
+ return (f"Unknown reaction setting `{setting}`" if NO_ANSI else f"Unknown reaction setting \033[1m{setting}\033[22m")
4469
4471
 
4470
4472
  if arguments[0] == "add" and len(arguments) >= 2 and arguments[1].lower() == "catalyst":
4471
4473
  if len(arguments) < 3:
@@ -4523,7 +4525,7 @@ def execute(arguments, query_text=None, positions=None):
4523
4525
 
4524
4526
  for attribute in return_attributes:
4525
4527
  if attribute not in moleculeKeys:
4526
- return f"There is no key `{attribute}`"
4528
+ return (f"There is no key `{attribute}`" if NO_ANSI else f"There is no key \033[1m{attribute}\033[22m")
4527
4529
 
4528
4530
  if return_index != 2:
4529
4531
  return "Usage: findmol <name-or-formula> [return <attribute> ...]"
@@ -4540,7 +4542,7 @@ def execute(arguments, query_text=None, positions=None):
4540
4542
  result = fetch_pubchem_molecule(value)
4541
4543
 
4542
4544
  if result is None:
4543
- return f"Could not find molecule `{value}`"
4545
+ return (f"Could not find molecule `{value}`" if NO_ANSI else f"Could not find molecule \033[1m{value}\033[22m")
4544
4546
 
4545
4547
  if isinstance(result, str) and "PubChem is currently unavailable" in result:
4546
4548
  return result
@@ -4576,7 +4578,7 @@ def execute(arguments, query_text=None, positions=None):
4576
4578
 
4577
4579
  for attribute in return_attributes:
4578
4580
  if attribute not in reactionKeys:
4579
- return f"There is no key `{attribute}`"
4581
+ return (f"There is no key `{attribute}`" if NO_ANSI else f"There is no key \033[1m{attribute}\033[22m")
4580
4582
 
4581
4583
  if return_index != 2:
4582
4584
  return "Usage: findre <name-or-id> [return <attribute> ...]"
@@ -4594,7 +4596,7 @@ def execute(arguments, query_text=None, positions=None):
4594
4596
  if result == [] or result == "No matching item":
4595
4597
  result = find_reaction(value, external=True)
4596
4598
  if result is None:
4597
- return f"Could not find reaction `{value}` in Reactions.json, Rhea, or Chempirical"
4599
+ return (f"Could not find reaction `{value}` in Reactions.json, Rhea, or Chempirical" if NO_ANSI else f"Could not find reaction \033[1m{value}\033[22m in Reactions.json, Rhea, or Chempirical")
4598
4600
 
4599
4601
  if return_attributes:
4600
4602
  if isinstance(result, list):
@@ -4621,10 +4623,10 @@ def execute(arguments, query_text=None, positions=None):
4621
4623
  source_name = arguments[1]
4622
4624
 
4623
4625
  if source_name not in SOURCES:
4624
- return f"There is no source `{source_name}`"
4626
+ return (f"There is no source `{source_name}`" if NO_ANSI else f"There is no source \033[1m{source_name}\033[22m")
4625
4627
 
4626
4628
  current_source = source_name
4627
- return f"Source set to `{source_name}`"
4629
+ return (f"Source set to `{source_name}`" if NO_ANSI else f"Source set to \033[1m{source_name}\033[22m")
4628
4630
 
4629
4631
  if arguments[0] == "list":
4630
4632
  if len(arguments) != 2 or arguments[1] != "all":
@@ -4660,8 +4662,8 @@ def execute(arguments, query_text=None, positions=None):
4660
4662
  if source_name is None:
4661
4663
  return (
4662
4664
  "No source selected. "
4663
- "Specify one, e.g. "
4664
- "`search all elements atomic_mass < 100`"
4665
+ "Specify one, e.g. "+
4666
+ ("`search all elements atomic_mass < 100`" if NO_ANSI else "\033[1msearch all elements atomic_mass < 100\033[22m")
4665
4667
  )
4666
4668
 
4667
4669
  source, keys = SOURCES[source_name]
@@ -4682,7 +4684,7 @@ def execute(arguments, query_text=None, positions=None):
4682
4684
  absolute_index = return_index + 1 + attribute_index
4683
4685
  pos, length = error_position(positions, absolute_index) if positions else (None, 1)
4684
4686
  return format_error(
4685
- f"There is no key `{attribute}`",
4687
+ (f"There is no key `{attribute}`" if NO_ANSI else f"There is no key \033[1m{attribute}\033[22m"),
4686
4688
  query_text,
4687
4689
  pos,
4688
4690
  length
@@ -4718,9 +4720,9 @@ def execute(arguments, query_text=None, positions=None):
4718
4720
 
4719
4721
  if isinstance(results, int):
4720
4722
  if mode != "all":
4721
- return format_error("`count` cannot be combined with `first` or `last`")
4723
+ return format_error(("`count` cannot be combined with `first` or `last`" if NO_ANSI else "\033[1mcount\033[22m cannot be combined with \033[1mfirst\033[22m or \033[1mlast\033[22m"))
4722
4724
  if return_attributes is not None:
4723
- return format_error("`return` cannot be used with `count`")
4725
+ return format_error(("`return` cannot be used with `count`" if NO_ANSI else "\033[1mreturn\033[22m cannot be used with \033[1mcount\033[22m"))
4724
4726
  return results
4725
4727
 
4726
4728
  # `return` is a projection. It intentionally happens after
@@ -4758,13 +4760,13 @@ def execute(arguments, query_text=None, positions=None):
4758
4760
  if query_text is not None and positions:
4759
4761
  pos, length = error_position(positions, 0)
4760
4762
  return format_error(
4761
- f"Unknown command `{arguments[0]}`",
4763
+ (f"Unknown command `{arguments[0]}`" if NO_ANSI else f"Unknown command \033[1m{arguments[0]}\033[22m"),
4762
4764
  query_text,
4763
4765
  pos,
4764
4766
  length
4765
4767
  )
4766
4768
 
4767
- return format_error(f"Unknown command `{arguments[0]}`")
4769
+ return format_error((f"Unknown command `{arguments[0]}`" if NO_ANSI else f"Unknown command \033[1m{arguments[0]}\033[22m"))
4768
4770
 
4769
4771
 
4770
4772
  HELP = (
@@ -5310,7 +5312,7 @@ class ChemQLQuery:
5310
5312
  return ChemQLQuery(f"{self.query} intersection {other.query}")
5311
5313
 
5312
5314
  def __str__(self):
5313
- return f"Query of `{self.query}`"
5315
+ return (f"Query of `{self.query}`" if NO_ANSI else f"Query of \033[1m{self.query}\033[22m")
5314
5316
 
5315
5317
  def __repr__(self):
5316
5318
  return str(self)
@@ -5479,7 +5481,7 @@ def _unclosed_expression_delimiters(lines):
5479
5481
  return expected
5480
5482
 
5481
5483
 
5482
- def main():
5484
+ def main(ANSI: bool = True):
5483
5485
  parser = argparse.ArgumentParser(
5484
5486
  description="Chemql query and chemistry language"
5485
5487
  )
@@ -5542,7 +5544,7 @@ def main():
5542
5544
  sys.exit(upgrade_package(version_target, args.force))
5543
5545
 
5544
5546
  global NO_ANSI
5545
- NO_ANSI = args.no_ansi or bool(args.output)
5547
+ NO_ANSI = not ANSI or args.no_ansi or bool(args.output)
5546
5548
 
5547
5549
  if args.output and args.command is None and args.file is None:
5548
5550
  parser.error("-o/--output requires -c/--command or -f/--file")
@@ -5820,7 +5822,7 @@ def run_interactive():
5820
5822
  lines = []
5821
5823
  break
5822
5824
  except EOFError:
5823
- message = "Missing `[endblock]`"
5825
+ message = ("Missing `[endblock]`" if NO_ANSI else "Missing \033[1m[endblock]\033[22m")
5824
5826
  print("\n" + (message if NO_ANSI else "\033[31mMissing `\033[1m[endblock]\033[22m`\033[0m"))
5825
5827
  lines = []
5826
5828
  break
@@ -5843,4 +5845,4 @@ def run_interactive():
5843
5845
 
5844
5846
 
5845
5847
  if __name__ == "__main__":
5846
- main()
5848
+ main(True)
@@ -1,4 +1,4 @@
1
1
  from . import main
2
2
 
3
3
 
4
- main()
4
+ main(True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chemql-python
3
- Version: 1.3.1
3
+ Version: 1.3.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
@@ -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.3.1`
22
+ - **Version:** `1.3.2`
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