I want to highlight Python syntax errors in browser.
I discovered there is LSP implementation for Python and LSP client for Monaco editor.
Is there any way to connect them together?
There is a way to connect them together!
It is all about the Language Server Protocol.
First step: the Language Server
The first thing you need is a running server that will provide language-specific logic (such as autocompletion, validation, etc.).
As mentioned in your question, you can use palantir's python-language-server.
You can also find a list of existing language server implementations by language on langserver.org.
In LSP, client and server are meant to communicate via a JSON-RPC websocket.
You can use python-jsonrpc-server and execute this python script to launch a python language server on your device:
python langserver_ext.py
This will host the language server on ws://localhost:3000/python.
Second step: the Language Client
Monaco is initially part of VSCode. Most existing LSP client parts for Monaco are thus initially meant for VSCode, so you will need to use a bit of nodejs and npm.
There exist a lot of modules to link monaco and an LSP client, some with vscode, some not - and it becomes very time-consuming to get this sorted out.
Here is a list of modules I used and finally got to work:
#codingame/monaco-languageclient
#codingame/monaco-jsonrpc
normalize-url
reconnecting-websocket
Using server-side javascript on a browser
Now, the neat part: node modules are server-side javascript. Which means, you can't use them within a browser directly (see It is not possible to use RequireJS on the client (browser) to access files from node_modules.).
You need to use a build tool, like browserify to transpile your node modules to be usable by a browser:
.../node_modules/#codingame/monaco-languageclient/lib$ browserify monaco-language-client.js monaco-services.js connection.js ../../monaco-jsonrpc/lib/connection.js -r ./vscode-compatibility.js:vscode > monaco-jsonrpc-languageclient.js
This will create a file named monaco-jsonrpc-languageclient.js, which we will use as a bundle for both monaco-languageclient and monaco-jsonrpc.
Notes:
The -r ./vscode-compatibility.js:vscode tells browserify to use the vscode-compatibility.js file for every dependency to the vscode module (see Unresolved dependencies to 'vscode' when wrapping monaco-languageclient).
We browserify these modules as a single bundle, to avoid multiple inclusions of some dependencies.
Now that you have a browser-compatible javascript file, you need to make needed components visible (ie. export them as window properties).
In monaco-jsonrpc-languageclient.js, search for places where MonacoLanguageClient, createConnection, MonacoServices, listen, ErrorAction, and CloseAction are exported. There, add a line to glabally export them:
(...)
exports.MonacoLanguageClient = MonacoLanguageClient;
window.MonacoLanguageClient = MonacoLanguageClient; // Add this line
(...)
exports.createConnection = createConnection;
window.createConnection = createConnection; // Add this line
(...)
(MonacoServices = exports.MonacoServices || (exports.MonacoServices = {}));
window.MonacoServices = MonacoServices; // Add this line
(...)
etc.
Do the same operation for normalize-url:
.../node_modules/normalize-url/lib$ browserify index.js > normalizeurl.js
In normalizeurl.js, search for the place where normalizeUrl is exported. There (or, as default, at the end of the file), add a line to globally export it:
window.normalizeUrl = normalizeUrl;
And you can do the same operation for reconnecting-websocket, or use the amd version that is shipped with the module.
Include monaco-jsonrpc-languageclient.js, normalizeurl.js and the browserified or AMD reconnecting-websocket module on your page.
For faster loading time, you can also minify them with a minifying tool (like uglify-js).
Finally, we can create and connect the client:
// From https://github.com/TypeFox/monaco-languageclient/blob/master/example/src/client.ts
/* --------------------------------------------------------------------------------------------
* Copyright (c) 2018 TypeFox GmbH (http://www.typefox.io). All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
MonacoServices.install(monaco); // This is what links everything with your monaco editors.
var url = 'ws://localhost:3000/python';
// Create the web socket.
var webSocket = new ReconnectingWebSocket(normalizeUrl(url), [], {
maxReconnectionDelay: 10000,
minReconnectionDelay: 1000,
reconnectionDelayGrowFactor: 1.3,
connectionTimeout: 10000,
maxRetries: Infinity,
debug: false
});
// Listen when the web socket is opened.
listen({
webSocket,
onConnection: function(connection) {
// create and start the language client
var languageClient = new MonacoLanguageClient({
name: 'Python Language Client',
clientOptions: {
// use a language id as a document selector
documentSelector: ['python'],
// disable the default error handler
errorHandler: {
error: () => ErrorAction.Continue,
closed: () => CloseAction.DoNotRestart
}
},
// create a language client connection from the JSON RPC connection on demand
connectionProvider: {
get: (errorHandler, closeHandler) => {
return Promise.resolve(createConnection(connection, errorHandler, closeHandler));
}
}
});
var disposable = languageClient.start();
connection.onClose(() => disposable.dispose());
}
});
Related
I have the python language server running on a port and now need to open a websocket in my react app to talk to the PYLS.
I'm using monaco-languageclient, and this line specifically fails compilation:
MonacoServices.install(editor); //MonacoServices is from monaco-languageclient
Failed to compile.
./node_modules/vscode-languageclient/lib/client.js
Module not found: Can't resolve 'vscode' in '/node_modules/vscode-languageclient/lib'
There shouldn't be a dependency to vscode, right?
This person found a way around it by editing their webpack settings, but to do that, I'd have to eject https://stackoverflow.com/a/56644955/3344422
Is there another way around this?
(I'm using this monaco library so I didn't have to edit my webpack config https://github.com/suren-atoyan/monaco-react#editor-instance )
You can use customize-cra to edit webpack config without ejecting, and provide it with a config-overrides.js where you package.json lives.
/* config-overrides.js */
const { useBabelRc, override, setWebpackTarget, addWebpackPlugin, addWebpackAlias } = require('customize-cra')
//Monoco support for WebPack mode
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
module.exports = override(
setWebpackTarget("electron-renderer"),
useBabelRc(),
addWebpackPlugin(new MonacoWebpackPlugin()),
addWebpackAlias({'vscode': require.resolve('monaco-languageclient/lib/vscode-compatibility')}),
);
I have a simple python script: the generates an x-icon from a hex colour given to it, then it returns a valid byte-stream (BytesIO).
I want to get something like this (please, do not laugh, I'm using Nginx for about two days):
location ~^/icons/(?<colour>[a-fA-F0-9]{6})\.ico$ {
send 200 (./favicon.py colour); # System call to `favicon.py` with `colour` argument.
}
Is it possible at all?
The following config should do the work:
location ~^/icons/(?<colour>[a-fA-F0-9]{6})\.ico$ {
content_by_lua '
local command = "./favicon.py colour"
local handle = io.popen(command)
local content = handle:read("*a")
handle:close()
ngx.print(content)
';
}
Basically it uses Lua for executing and providing the content
NOTE: your nginx should be compiled with the lua module for this solution to work
I am working on a CEFPython application that requires me to include some external files like JS or CSS libraries.However any external path (Referring to external libraries present in the same folder and the online resource URL both) mentioned in the HTML file seems to be unacceptable, I am sure am missing a flag to enable external files liking but am unable to figure out which. below is the code to my main function:
def main():
sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error
# To change user agent use either "product_version"
# or "user_agent" options. Explained in Tutorial in
# "Change user agent string" section.
settings = {
# "web_security_disabled": True,
# "user_agent": "MyAgent/20.00 MyProduct/10.00",
}
cef.Initialize(settings=settings)
browser = cef.CreateBrowserSync(url=html_to_data_uri(HTML_code),
window_title="Demo Program")
set_javascript_bindings(browser)
cef.MessageLoop()
cef.Shutdown()
the "web_security_disabled" setting must be passed in the browser creation, not in the cef initialization.
Example :
settings = {
"web_security_disabled": True,
}
def launcher(url):
sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error
cef.Initialize()
cef.CreateBrowserSync(url=url, window_title="my title", settings=settings)
cef.MessageLoop()
cef.Shutdown()
You cannot mix scripts from different origins, see:
https://en.wikipedia.org/wiki/Same-origin_policy
There is --disable-web-security switch that you can try.
If that doesn't work then use "http" url instead of data uri. You can run an internal web server using Python. You can also implement ResourceHandler and serve http content without the need to run a web server, however implementing ResourceHandler correctly is a complex task.
I have a node web client called bidsell, and a small Python Tornado REST API called quote. Bidsell when triggered makes regular http get calls to quote. Quote duely returns random price information as json. Works locally - want to share it online, but how? Heroku looks promising. Have tried already to deploy both bidsell and quote in the same project on heroku, each running within their own heroku web dyno or deployment container. From the logs "heroku log" both are installed correctly but only one appears to be running. So I can access the front page url of bidsell, for example, but when bidsell is triggered to go fetch quote info the quote service is not found :-( Should I be using another deployment pattern?
ok so as jr0cket suggested I created 2 heroku projects - one for the bidsell node project and one for the quote service.
In addition to the bidsell node project source files I had a procfile containing the following:
web: npm start
and a scripts section in package.json informing heroku how to start the app:
"scripts": {
"start": "gulp serve"
}
In addition to the quoteService source python file I had a procfile containing the following:
web: python quoteService.py
and a requirements.txt file containing:
tornado==3.1.1
pyrestful==0.4.1
Had the following proxy.js as middleware in the bidsell app:
'use strict';
var proxyMiddleware = require('http-proxy-middleware');
var options = {
target: 'http://quoteservce.herokuapp.com:80',
changeOrigin: true
};
var proxy = proxyMiddleware('/quote', options);
module.exports = function(){
return [proxy];
}
being called from server.js:
'use strict';
..
var middleware = require('./proxy');
module.exports = function(options) {
function browserSyncInit(baseDir, browser) {
browser = browser === undefined ? 'default' : browser;
..
var server = {
baseDir: baseDir,
routes: routes
};
server.middleware = middleware();
browserSync.instance = browserSync.init({
port: (process.env.PORT || 5000),
startPath: '/',
server: server,
browser: browser
});
}
..
gulp.task('serve', ['watch'], function () {
browserSyncInit([options.tmp + '/serve', options.src]);
});
..
};
to allow communication between bidsell and quoteService. For further background info take a look here
The running app you can find here.
May take a little while for the idle free-tier heroku dynos to fire up ;-)
Bidsell project on git.
QuoteService project on git.
As your project is two seperate technology stacks, the easiest approach is to deploy these as two separate Heroku apps. This gives you simple way to create the specific environment (languages, runtimes, libraries) needed for each app / service.
You could create an Heroku configuration variable QUOTE_REST_API for the node web client that points to the external web address. For example, using the heroku toolbelt
heroku config:set QUOTE_REST_API=https://quote-api.herokuapp.com/
Using the QUOTE_REST_API configuration variable in your node client would give a simple way to change the address of the quote without having to change your code.
If you are running two separate projects in one Heroku app, you need to ensure that you have two web: entries for the Procfile to start your separate processes. Only processes marked as web will listen to web traffic.
You may not be able to run two different web processes if you use the free tier of heroku.
BrowserStack is a powerful platform for testing web sites against the most
current and modern browser. So far so good.
BrowserStack also provides an API
The API has the concept of a worker representing a specific browser (version) loading a particular URL.
What useful things can I do with such a worker instance?
How would one integrate such a worker with Selenium tests?
How would one integrate such a worker with unittests (Python)?
How would one use such a worker e.g. for testing if a particular website with a video player would actually load and play a video (e.g. for cross-browser video testing)?
Current API opens your provided url in all platform/browser combinations.
So, if you open an HTML page with lot of JS tests, you need to be using tool like yeti/testswarm/js-test-driver which automatically fetch results from browser.
Another example of using BrowserStack API is http://ryanseddon.github.com/bunyip/
Sample integration with Jenkins: http://github.com/jquery/testswarm/wiki/Automated-Distributed-Continuous-Integration-for-JavaScript
For local JS testing, you will need to use tools like localtunnel to get a public url for your local servers.
One of the most useful capabilities of the current BrowserStack API is to allow you to mark a session as a failed test.
Like any Selenium hub/node system, BrowserStack doesn't know why you're sending commands to the browser. It just runs the commands you request. Consequently, it has no way to know when a test fails.
But you can use the API to tell it that a test failed, so that the session gets marked as failed in the BrowserStack UI. Then you can filter on just the failed sessions to investigate them.
This is in Java, not Python, but here's some sample code that shows how to update sessions to reflect that they represent failed tests. You just pass in the Selenium session IDs (which you need to save as you run the test in question) and the exception you got when the test failed.
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import com.unblu.automation.support.settings.Prop;
import com.unblu.automation.support.settings.Settings;
import org.openqa.selenium.remote.SessionId;
public class BrowserStackUpdater {
private void markSessionAsFailed(SessionId sessionId, Throwable e) {
var url = "https://api.browserstack.com/automate/sessions/" + sessionId + ".json";
try {
var userName = "BROWSERSTACKUSERNAMEHERE";
var key = "BROWSERSTACKKEYHERE";
var result = Unirest.put(url)
.basicAuth(userName, key)
.field("status", "failed")
.field("reason", e.toString())
.asString();
System.out.println("Marking test failed; reply from BrowserStack: " +
result.getStatus() + " : " + result.getBody());
}
catch (UnirestException ue) { ue.printStackTrace(); }
}
public void markTestFailedInBrowserStack(Iterable<SessionId> sessionIds, Throwable e) {
var env = Settings.getString(Prop.runEnvironment);
if (env.equals("BrowserStack")) {
for (var sid : sessionIds) {
markSessionAsFailed(sid, e);
}
}
}
}