Skip to content

Commit 95feda2

Browse files
authored
Create a python daemon (microsoft#8420)
1 parent 764106b commit 95feda2

26 files changed

Lines changed: 1291 additions & 32 deletions

news/3 Code Health/8451.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Create python daemon for execution of python code.

package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2738,6 +2738,7 @@
27382738
"vscode-debugadapter": "^1.28.0",
27392739
"vscode-debugprotocol": "^1.28.0",
27402740
"vscode-extension-telemetry": "0.1.0",
2741+
"vscode-jsonrpc": "^4.0.0",
27412742
"vscode-languageclient": "^5.2.1",
27422743
"vscode-languageserver": "^5.2.1",
27432744
"vscode-languageserver-protocol": "^3.14.1",
@@ -2769,6 +2770,7 @@
27692770
"@types/chai-arrays": "^1.0.2",
27702771
"@types/chai-as-promised": "^7.1.0",
27712772
"@types/copy-webpack-plugin": "^4.4.2",
2773+
"@types/dedent": "^0.7.0",
27722774
"@types/del": "^3.0.0",
27732775
"@types/diff-match-patch": "^1.0.32",
27742776
"@types/download": "^6.2.2",
@@ -2831,6 +2833,7 @@
28312833
"css-loader": "^1.0.1",
28322834
"cucumber-html-reporter": "^4.0.5",
28332835
"decache": "^4.4.0",
2836+
"dedent": "^0.7.0",
28342837
"del": "^3.0.0",
28352838
"download": "^7.0.0",
28362839
"electron-download": "^4.1.1",

pvsc.code-workspace

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
},
66
{
77
"path": "uitests"
8+
},
9+
{
10+
"path": "pythonFiles"
811
}
912
],
1013
"settings": {

pythonFiles/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PYTHONPATH=./lib/python

pythonFiles/.vscode/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"files.exclude": {
3+
"**/__pycache__/**": true,
4+
"**/**/*.pyc": true
5+
},
6+
"python.formatting.provider": "black"
7+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Sample usage in node.js
2+
3+
```javascript
4+
const cp = require('child_process');
5+
const rpc = require('vscode-jsonrpc');
6+
const env = {
7+
PYTHONUNBUFFERED: '1',
8+
PYTHONPATH: '<extension dir>/pythonFiles:<extension dir>/pythonFiles/lib/python'
9+
}
10+
const childProcess = cp.spawn('<fully qualifieid python path>', ['-m', 'datascience.daemon', '-v', '--log-file=log.log'], {env});
11+
const connection = rpc.createMessageConnection(new rpc.StreamMessageReader(childProcess.stdout),new rpc.StreamMessageWriter(childProcess.stdin));
12+
13+
connection.onClose(() => console.error('Closed'));
14+
connection.onError(ex => console.error(ex));
15+
connection.onDispose(() => console.error('disposed'));
16+
connection.onNotification((e, data) => console.log(`Notification from daemon, such as stdout/stderr, ${JSON.stringify(data)}`);
17+
connection.onUnhandledNotification(e => console.error(e));
18+
19+
// Start
20+
connection.listen();
21+
22+
const pingRequest = new rpc.RequestType('ping');
23+
connection.sendRequest(pingRequest, {data: 'one₹😄'})
24+
.then(response => console.log(`Pong received ${JSON.stringify(response)}`), ex => console.error(ex));
25+
```
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import argparse
5+
import importlib
6+
import json
7+
import logging
8+
import logging.config
9+
import sys
10+
11+
12+
log = logging.getLogger(__name__)
13+
14+
LOG_FORMAT = "%(asctime)s UTC - %(levelname)s - %(name)s - %(message)s"
15+
16+
17+
def add_arguments(parser):
18+
parser.description = "Daemon"
19+
20+
parser.add_argument(
21+
"--daemon-module",
22+
default="datascience.daemon.daemon_python",
23+
help="Daemon Module",
24+
)
25+
26+
log_group = parser.add_mutually_exclusive_group()
27+
log_group.add_argument(
28+
"--log-config", help="Path to a JSON file containing Python logging config."
29+
)
30+
log_group.add_argument(
31+
"--log-file",
32+
help="Redirect logs to the given file instead of writing to stderr."
33+
"Has no effect if used with --log-config.",
34+
)
35+
36+
parser.add_argument(
37+
"-v",
38+
"--verbose",
39+
action="count",
40+
default=0,
41+
help="Increase verbosity of log output, overrides log config file",
42+
)
43+
44+
45+
def _configure_logger(verbose=0, log_config=None, log_file=None):
46+
root_logger = logging.root
47+
48+
if log_config:
49+
with open(log_config, "r") as f:
50+
logging.config.dictConfig(json.load(f))
51+
else:
52+
formatter = logging.Formatter(LOG_FORMAT)
53+
if log_file:
54+
log_handler = logging.handlers.RotatingFileHandler(
55+
log_file,
56+
mode="a",
57+
maxBytes=50 * 1024 * 1024,
58+
backupCount=10,
59+
encoding=None,
60+
delay=0,
61+
)
62+
else:
63+
log_handler = logging.StreamHandler()
64+
log_handler.setFormatter(formatter)
65+
root_logger.addHandler(log_handler)
66+
67+
if verbose == 0:
68+
level = logging.WARNING
69+
elif verbose == 1:
70+
level = logging.INFO
71+
elif verbose >= 2:
72+
level = logging.DEBUG
73+
74+
root_logger.setLevel(level)
75+
76+
77+
def main():
78+
""" Starts the daemon.
79+
The daemon_module allows authors of modules to provide a custom daemon implementation.
80+
E.g. we have a base implementation for standard python functionality,
81+
and a custom daemon implementation for DS work (related to jupyter).
82+
"""
83+
parser = argparse.ArgumentParser()
84+
add_arguments(parser)
85+
args = parser.parse_args()
86+
_configure_logger(args.verbose, args.log_config, args.log_file)
87+
88+
log.info("Starting daemon from %s.PythonDaemon", args.daemon_module)
89+
try:
90+
daemon_module = importlib.import_module(args.daemon_module)
91+
daemon_cls = daemon_module.PythonDaemon
92+
daemon_cls.start_daemon()
93+
except Exception:
94+
import traceback
95+
96+
log.error(traceback.format_exc())
97+
raise Exception("Failed to start daemon")
98+
99+
100+
if __name__ == "__main__":
101+
main()

0 commit comments

Comments
 (0)