chemql-python 1.0.0__py3-none-any.whl → 1.0.2__py3-none-any.whl
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/__init__.py +168 -15
- {chemql_python-1.0.0.dist-info → chemql_python-1.0.2.dist-info}/METADATA +71 -1
- {chemql_python-1.0.0.dist-info → chemql_python-1.0.2.dist-info}/RECORD +7 -7
- {chemql_python-1.0.0.dist-info → chemql_python-1.0.2.dist-info}/WHEEL +0 -0
- {chemql_python-1.0.0.dist-info → chemql_python-1.0.2.dist-info}/entry_points.txt +0 -0
- {chemql_python-1.0.0.dist-info → chemql_python-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {chemql_python-1.0.0.dist-info → chemql_python-1.0.2.dist-info}/top_level.txt +0 -0
chemql/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import math
|
|
3
3
|
import shlex
|
|
4
|
+
import os
|
|
4
5
|
import re
|
|
5
6
|
import ast
|
|
6
7
|
import argparse
|
|
@@ -10,6 +11,8 @@ import sys
|
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from prompt_toolkit import PromptSession
|
|
12
13
|
from prompt_toolkit.lexers import Lexer
|
|
14
|
+
import atexit
|
|
15
|
+
import uuid
|
|
13
16
|
from prompt_toolkit.styles import Style
|
|
14
17
|
from prompt_toolkit.completion import Completer, Completion
|
|
15
18
|
import urllib.parse
|
|
@@ -34,8 +37,95 @@ __all__ = [
|
|
|
34
37
|
"search",
|
|
35
38
|
"union",
|
|
36
39
|
"intersection",
|
|
40
|
+
"__version__"
|
|
37
41
|
]
|
|
38
42
|
|
|
43
|
+
__version__ = "1.0.2"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_shell_process = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_shell():
|
|
50
|
+
global _shell_process
|
|
51
|
+
|
|
52
|
+
if _shell_process is None or _shell_process.poll() is not None:
|
|
53
|
+
if os.name == "nt":
|
|
54
|
+
_shell_process = subprocess.Popen(
|
|
55
|
+
["cmd.exe", "/D", "/Q", "/K", "prompt $S"],
|
|
56
|
+
stdin=subprocess.PIPE,
|
|
57
|
+
stdout=subprocess.PIPE,
|
|
58
|
+
stderr=subprocess.STDOUT,
|
|
59
|
+
text=True,
|
|
60
|
+
bufsize=1,
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
_shell_process = subprocess.Popen(
|
|
64
|
+
["bash", "--noprofile", "--norc"],
|
|
65
|
+
stdin=subprocess.PIPE,
|
|
66
|
+
stdout=subprocess.PIPE,
|
|
67
|
+
stderr=subprocess.STDOUT,
|
|
68
|
+
text=True,
|
|
69
|
+
bufsize=1,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return _shell_process
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def run_shell_command(command):
|
|
76
|
+
shell = get_shell()
|
|
77
|
+
|
|
78
|
+
command = command.replace("\r\n", "\n").replace("\r", "\n").rstrip("\n")
|
|
79
|
+
marker = f"__CHEMQL_END_{uuid.uuid4().hex}__"
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
if os.name == "nt":
|
|
83
|
+
# Run the command, then print a unique completion marker.
|
|
84
|
+
shell.stdin.write(
|
|
85
|
+
f"{command}\r\n"
|
|
86
|
+
f"echo {marker}\r\n"
|
|
87
|
+
)
|
|
88
|
+
else:
|
|
89
|
+
shell.stdin.write( #type:ignore
|
|
90
|
+
f"{command}\n"
|
|
91
|
+
f"printf '\\n%s\\n' '{marker}'\n"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
shell.stdin.flush() #type:ignore
|
|
95
|
+
|
|
96
|
+
output = []
|
|
97
|
+
|
|
98
|
+
while True:
|
|
99
|
+
line = shell.stdout.readline() #type:ignore
|
|
100
|
+
|
|
101
|
+
if not line:
|
|
102
|
+
return "\n".join(output) or "Shell session terminated."
|
|
103
|
+
|
|
104
|
+
line = line.rstrip("\r\n")
|
|
105
|
+
|
|
106
|
+
if marker in line:
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
output.append(line)
|
|
110
|
+
|
|
111
|
+
return "\n".join(output).strip()
|
|
112
|
+
|
|
113
|
+
except (BrokenPipeError, OSError) as exc:
|
|
114
|
+
return f"Shell error: {exc}"
|
|
115
|
+
|
|
116
|
+
def close_shell():
|
|
117
|
+
"""Clean up the persistent shell on exit."""
|
|
118
|
+
global _shell_process
|
|
119
|
+
|
|
120
|
+
if _shell_process is not None:
|
|
121
|
+
if _shell_process.poll() is None:
|
|
122
|
+
_shell_process.terminate()
|
|
123
|
+
_shell_process.wait(timeout=2)
|
|
124
|
+
|
|
125
|
+
_shell_process = None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
atexit.register(close_shell)
|
|
39
129
|
|
|
40
130
|
class Unknown:
|
|
41
131
|
"""Represents a Chemql query result that cannot be resolved to a known object."""
|
|
@@ -1623,7 +1713,7 @@ def _space_query_operators(text):
|
|
|
1623
1713
|
out.append(ch)
|
|
1624
1714
|
if escaped:
|
|
1625
1715
|
escaped = False
|
|
1626
|
-
elif ch == "
|
|
1716
|
+
elif ch == "\\":
|
|
1627
1717
|
escaped = True
|
|
1628
1718
|
elif ch == quote:
|
|
1629
1719
|
quote = None
|
|
@@ -1666,7 +1756,7 @@ def _split_set_operations(text):
|
|
|
1666
1756
|
ch = text[j]
|
|
1667
1757
|
if quote:
|
|
1668
1758
|
if escaped: escaped = False
|
|
1669
|
-
elif ch == "
|
|
1759
|
+
elif ch == "\\": escaped = True
|
|
1670
1760
|
elif ch == quote: quote = None
|
|
1671
1761
|
elif ch in ("'", '"'): quote = ch
|
|
1672
1762
|
elif ch == "(": depth += 1
|
|
@@ -2097,16 +2187,11 @@ def process_inline(text):
|
|
|
2097
2187
|
if not stripped[end + 2:].strip():
|
|
2098
2188
|
return execute_query_block(stripped[2:end].strip())
|
|
2099
2189
|
|
|
2100
|
-
# Backtick-wrapped commands
|
|
2190
|
+
# Backtick-wrapped commands use the persistent Bash session.
|
|
2101
2191
|
if len(stripped) >= 2 and stripped.startswith("`") and stripped.endswith("`"):
|
|
2102
2192
|
command = stripped[1:-1]
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
if completed.stderr:
|
|
2106
|
-
output += completed.stderr
|
|
2107
|
-
if completed.returncode and not output:
|
|
2108
|
-
output = f"Command exited with status {completed.returncode}"
|
|
2109
|
-
return output.rstrip("\\n")
|
|
2193
|
+
return run_shell_command(command)
|
|
2194
|
+
|
|
2110
2195
|
|
|
2111
2196
|
# Everything else is query language.
|
|
2112
2197
|
return execute_query_text(stripped)
|
|
@@ -2458,6 +2543,9 @@ def execute(arguments, query_text=None, positions=None):
|
|
|
2458
2543
|
if arguments[0].lower() == "sorry":
|
|
2459
2544
|
return "\033[34mIt's ok, everybody makes mistakes👌\033[0m"
|
|
2460
2545
|
|
|
2546
|
+
if arguments[0].lower() == "version":
|
|
2547
|
+
return __version__
|
|
2548
|
+
|
|
2461
2549
|
if arguments[0] == "conditions":
|
|
2462
2550
|
if len(arguments) != 1:
|
|
2463
2551
|
return "Usage: conditions"
|
|
@@ -3042,7 +3130,7 @@ class ChemqlLexer(Lexer):
|
|
|
3042
3130
|
first_word = words[0] if words else ""
|
|
3043
3131
|
|
|
3044
3132
|
# search / source / list / return / clear / exit
|
|
3045
|
-
if first_word in {"search", "source", "list", "clear", "exit", "findel", "findmol", "findre", "set", "add", "remove", "react", "balance", "conditions", "view", "help", "dump"}:
|
|
3133
|
+
if first_word in {"search", "source", "list", "clear", "exit", "findel", "findmol", "findre", "set", "add", "remove", "react", "balance", "conditions", "view", "help", "dump", "version"}:
|
|
3046
3134
|
command_start = line.find(first_word)
|
|
3047
3135
|
command_end = command_start + len(first_word)
|
|
3048
3136
|
|
|
@@ -3290,7 +3378,7 @@ class ChemqlCompleter(Completer):
|
|
|
3290
3378
|
commands = (
|
|
3291
3379
|
"search", "source", "list", "clear", "return", "exit",
|
|
3292
3380
|
"findel", "findmol", "findre", "set", "add", "remove",
|
|
3293
|
-
"react", "balance", "conditions",
|
|
3381
|
+
"react", "balance", "conditions", "version", "help", "view"
|
|
3294
3382
|
)
|
|
3295
3383
|
|
|
3296
3384
|
if not command_words:
|
|
@@ -3433,6 +3521,54 @@ def intersection(x, y):
|
|
|
3433
3521
|
raise TypeError("intersection() expects two ChemQLQuery objects")
|
|
3434
3522
|
return x & y
|
|
3435
3523
|
|
|
3524
|
+
PACKAGE_NAME = "chemql-python"
|
|
3525
|
+
|
|
3526
|
+
def upgrade_package(version=None):
|
|
3527
|
+
"""Upgrade ChemQL, optionally to a specific version."""
|
|
3528
|
+
|
|
3529
|
+
if version is not None:
|
|
3530
|
+
# Accept ordinary numeric release versions, optionally with
|
|
3531
|
+
# prerelease or post-release suffixes.
|
|
3532
|
+
if not re.fullmatch(
|
|
3533
|
+
r"\d+(?:\.\d+)*(?:(?:a|b|rc)\d+)?(?:\.post\d+)?(?:\.dev\d+)?",
|
|
3534
|
+
version,
|
|
3535
|
+
re.IGNORECASE,
|
|
3536
|
+
):
|
|
3537
|
+
print(f"Invalid version: {version}", file=sys.stderr)
|
|
3538
|
+
return 2
|
|
3539
|
+
|
|
3540
|
+
target = f"{PACKAGE_NAME}=={version}"
|
|
3541
|
+
print(f"Installing {target}...")
|
|
3542
|
+
cmd = [
|
|
3543
|
+
sys.executable, "-m", "pip", "install",
|
|
3544
|
+
"--force-reinstall", target,
|
|
3545
|
+
]
|
|
3546
|
+
else:
|
|
3547
|
+
print(f"Upgrading {PACKAGE_NAME} to the latest version...")
|
|
3548
|
+
cmd = [
|
|
3549
|
+
sys.executable, "-m", "pip", "install",
|
|
3550
|
+
"--upgrade", PACKAGE_NAME,
|
|
3551
|
+
]
|
|
3552
|
+
|
|
3553
|
+
try:
|
|
3554
|
+
result = subprocess.run(cmd, check=False)
|
|
3555
|
+
if result.returncode != 0:
|
|
3556
|
+
print(
|
|
3557
|
+
f"Installation failed (exit code {result.returncode}).",
|
|
3558
|
+
file=sys.stderr,
|
|
3559
|
+
)
|
|
3560
|
+
return result.returncode
|
|
3561
|
+
|
|
3562
|
+
except KeyboardInterrupt:
|
|
3563
|
+
print("\nUpgrade cancelled.", file=sys.stderr)
|
|
3564
|
+
return 130
|
|
3565
|
+
except OSError as exc:
|
|
3566
|
+
print(f"Could not run pip: {exc}", file=sys.stderr)
|
|
3567
|
+
return 1
|
|
3568
|
+
|
|
3569
|
+
print("ChemQL installation completed.")
|
|
3570
|
+
return 0
|
|
3571
|
+
|
|
3436
3572
|
|
|
3437
3573
|
ChemQLQuery.__name__ = "ChemQLQuery"
|
|
3438
3574
|
ChemQLQuery.__qualname__ = "ChemQLQuery"
|
|
@@ -3459,14 +3595,31 @@ def main():
|
|
|
3459
3595
|
"-v",
|
|
3460
3596
|
"--version",
|
|
3461
3597
|
action="version",
|
|
3462
|
-
version=
|
|
3598
|
+
version=__version__,
|
|
3463
3599
|
)
|
|
3464
3600
|
|
|
3601
|
+
|
|
3602
|
+
parser.add_argument(
|
|
3603
|
+
"--upgrade",
|
|
3604
|
+
nargs="?",
|
|
3605
|
+
const="latest",
|
|
3606
|
+
default=None,
|
|
3607
|
+
metavar="VERSION",
|
|
3608
|
+
help="Upgrade ChemQL, optionally to a specific version.",
|
|
3609
|
+
)
|
|
3610
|
+
|
|
3611
|
+
|
|
3465
3612
|
args = parser.parse_args()
|
|
3466
3613
|
|
|
3467
|
-
if args.
|
|
3468
|
-
|
|
3614
|
+
if args.upgrade is not None:
|
|
3615
|
+
version_target = (
|
|
3616
|
+
None if args.upgrade == "latest" else args.upgrade
|
|
3617
|
+
)
|
|
3618
|
+
sys.exit(upgrade_package(version_target))
|
|
3469
3619
|
|
|
3620
|
+
if args.command is not None:
|
|
3621
|
+
process_lines(args.command.splitlines())
|
|
3622
|
+
return
|
|
3470
3623
|
elif args.file:
|
|
3471
3624
|
with open(args.file) as f:
|
|
3472
3625
|
code = f.read()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chemql-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.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
|
|
@@ -48,6 +48,25 @@ Install the Python package from PyPI:
|
|
|
48
48
|
pip install chemql-python
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
## Upgrading the package or switching versions
|
|
52
|
+
|
|
53
|
+
If your chemql-python version is >=1.0.1, you can simply run
|
|
54
|
+
```bash
|
|
55
|
+
chemql --upgrade
|
|
56
|
+
```
|
|
57
|
+
Or to switch versions to for example 0.2.2
|
|
58
|
+
```bash
|
|
59
|
+
chemql --upgrade 0.2.2
|
|
60
|
+
```
|
|
61
|
+
However if you do not have chemql-python version >=1.0.1
|
|
62
|
+
```bash
|
|
63
|
+
pip install --upgrade chemql-python
|
|
64
|
+
```
|
|
65
|
+
Or to switch versions to for example 0.2.2
|
|
66
|
+
```bash
|
|
67
|
+
pip install --force-reinstall chemql-python==0.2.2
|
|
68
|
+
```
|
|
69
|
+
|
|
51
70
|
## Quick start
|
|
52
71
|
|
|
53
72
|
Start the interactive terminal:
|
|
@@ -337,6 +356,57 @@ Python:
|
|
|
337
356
|
|
|
338
357
|
Only run Python code and shell commands from sources you trust.
|
|
339
358
|
|
|
359
|
+
## Shell Commands
|
|
360
|
+
|
|
361
|
+
ChemQL supports executing system shell commands by enclosing them in backticks (`` ` ``). Commands execute in a **persistent shell session**, allowing shell state to be preserved between commands.
|
|
362
|
+
|
|
363
|
+
**Syntax**
|
|
364
|
+
|
|
365
|
+
```chemql
|
|
366
|
+
`command`
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Examples**
|
|
370
|
+
|
|
371
|
+
Execute system commands:
|
|
372
|
+
|
|
373
|
+
```chemql
|
|
374
|
+
`whoami`
|
|
375
|
+
`dir`
|
|
376
|
+
`echo Hello`
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Change the working directory:
|
|
380
|
+
|
|
381
|
+
```chemql
|
|
382
|
+
`cd ..`
|
|
383
|
+
`cd ~`
|
|
384
|
+
`dir`
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
The working directory persists between commands.
|
|
388
|
+
|
|
389
|
+
Environment variables and other shell state can also persist between commands, depending on the shell being used.
|
|
390
|
+
|
|
391
|
+
**Platform support**
|
|
392
|
+
|
|
393
|
+
- **Linux:** Bash
|
|
394
|
+
- **macOS:** Bash
|
|
395
|
+
- **Windows:** Command Prompt (`cmd.exe`)
|
|
396
|
+
|
|
397
|
+
PowerShell is not supported by the current implementation.
|
|
398
|
+
|
|
399
|
+
**How it works**
|
|
400
|
+
|
|
401
|
+
ChemQL maintains a persistent shell process and sends commands to it sequentially. Command output is captured and displayed in the ChemQL interface, allowing shell state to persist throughout the session.
|
|
402
|
+
|
|
403
|
+
**Notes**
|
|
404
|
+
|
|
405
|
+
- Commands must be enclosed in backticks to be interpreted as shell commands rather than ChemQL statements.
|
|
406
|
+
- Shell syntax differs between operating systems. Use commands appropriate for your platform.
|
|
407
|
+
- Commands run with the permissions of the process running ChemQL and may modify files or affect your system.
|
|
408
|
+
- Interactive terminal programs and commands requiring direct terminal input may not work correctly because the shell uses pipes rather than a real terminal.
|
|
409
|
+
|
|
340
410
|
## Data and external services
|
|
341
411
|
|
|
342
412
|
ChemQL loads bundled JSON datasets for elements, molecules, reactions,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
chemql/__init__.py,sha256=
|
|
1
|
+
chemql/__init__.py,sha256=qKC-xi2NrsNahVsbsPJopByzy86UzKLbzO9SWSW_N-M,109826
|
|
2
2
|
chemql/__main__.py,sha256=3Q52s0hyuouhvSSqFEQFDr6mtJ-azkCwI2AD4c7f7A8,28
|
|
3
3
|
chemql/data/BondEnergies.json,sha256=QZN3iWZrD8125jV41EG1GiRohBtP0CzAP_EDQTfaEKc,8269
|
|
4
4
|
chemql/data/MoleculeBonds.json,sha256=Iw2shYxJScH_tI80IytBYoqVDSbgT7OSqPJUIgKZme4,84262
|
|
@@ -8,9 +8,9 @@ chemql/data/Reactions.json,sha256=BFpkKEWwLzG54ot96BVOWwU3btpimRJlOM57yGclGgc,15
|
|
|
8
8
|
chemql/data/THIRD_PARTY_LICENSES/PeriodicTableJSON.txt,sha256=9wih5q8s58qR3vJEGpooa4ESyAGZHcmGvzwMMkMHNKk,322
|
|
9
9
|
chemql/data/THIRD_PARTY_LICENSES/RHEA.txt,sha256=nXTeCP6vJBW5B_mtaVGFLr0w7vnjrSTBnv6_eOk3KKo,634
|
|
10
10
|
chemql/html/periodic_table.html,sha256=ftVgXWjRO2AsTAGEekTrvgE5ZvcwRfoby9UWoixc9Y8,361560
|
|
11
|
-
chemql_python-1.0.
|
|
12
|
-
chemql_python-1.0.
|
|
13
|
-
chemql_python-1.0.
|
|
14
|
-
chemql_python-1.0.
|
|
15
|
-
chemql_python-1.0.
|
|
16
|
-
chemql_python-1.0.
|
|
11
|
+
chemql_python-1.0.2.dist-info/licenses/LICENSE,sha256=o4ywHC0EwRy2zgIASiRD2sK3Vcsf7LBcTHjDvoJgRE0,1079
|
|
12
|
+
chemql_python-1.0.2.dist-info/METADATA,sha256=UXpbvKOb58-R9xDb2cIuAsXJp0d5bUDiNRCozeLnxes,11613
|
|
13
|
+
chemql_python-1.0.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
14
|
+
chemql_python-1.0.2.dist-info/entry_points.txt,sha256=j98ObVWR743an524z24AiWeRsppag-py5cc7l0D0HkI,39
|
|
15
|
+
chemql_python-1.0.2.dist-info/top_level.txt,sha256=9UzHwnk_F6W19Q9nayaFgVyAELQXXwoYjeBgB6MWNr8,7
|
|
16
|
+
chemql_python-1.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|