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.
- {chemql_python-1.3.1/src/chemql_python.egg-info → chemql_python-1.3.2}/PKG-INFO +2 -2
- {chemql_python-1.3.1 → chemql_python-1.3.2}/README.md +1 -1
- {chemql_python-1.3.1 → chemql_python-1.3.2}/pyproject.toml +1 -1
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/__init__.py +70 -68
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/__main__.py +1 -1
- {chemql_python-1.3.1 → chemql_python-1.3.2/src/chemql_python.egg-info}/PKG-INFO +2 -2
- {chemql_python-1.3.1 → chemql_python-1.3.2}/LICENSE +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/setup.cfg +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/data/BondEnergies.json +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/data/MoleculeBonds.json +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/data/Molecules.json +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/data/PeriodicTableJSON.json +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/data/Reactions.json +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/data/THIRD_PARTY_LICENSES/PeriodicTableJSON.txt +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/data/THIRD_PARTY_LICENSES/RHEA.txt +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql/html/periodic_table.html +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql_python.egg-info/SOURCES.txt +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql_python.egg-info/dependency_links.txt +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql_python.egg-info/entry_points.txt +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql_python.egg-info/requires.txt +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/src/chemql_python.egg-info/top_level.txt +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/tests/test_external_molecule_lookup.py +0 -0
- {chemql_python-1.3.1 → chemql_python-1.3.2}/tests/test_public_api.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
9
|
+
- **Version:** `1.3.2`
|
|
10
10
|
- **PyPI distribution:** `chemql-python`
|
|
11
11
|
- **Python import / CLI name:** `chemql`
|
|
12
12
|
- **Author:** Mohammad Rakibul Islam
|
|
@@ -52,7 +52,7 @@ __all__ = [
|
|
|
52
52
|
"__version__"
|
|
53
53
|
]
|
|
54
54
|
|
|
55
|
-
__version__ = "1.3.
|
|
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 =
|
|
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
|
|
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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chemql-python
|
|
3
|
-
Version: 1.3.
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|