Package: jyserver
Jyserver Web Framework with Pythonic Javascript SyntaxJyserver is a framework for simplifying the creation of font ends for apps and
kiosks by providing real-time access to the browser's DOM and Javascript from
the server using Python syntax. It also provides access to the Python code from
the browser's Javascript. It can be used stand-alone or with other
frameworks such as Flask, Django, etc.jyserver uses Python's dynamic syntax evaluation so that you can write
Python code that will dynamically be converted to JS and executed on the
browser. On the browser end, it uses JS's dynamic Proxy object to rewrite JS
code for execution by the server. All of this is done transparently without any
additional libraries or code. See examples below.Documentation:Class documentationGit (and examples):github:ftrias/jyserverTutorial:Dev.to articleTutorial Flask/Bottle:Dev.to Flask articleStandalone Example:fromjserverimportClient,ServerclassApp(Client):def__init__(self):# For simplicity, this is the web page we are rendering.# The module will add the relevant JS code to# make it all work. You can also use an html file.self.html="""<p id="time">TIME</p><button id="reset"onclick="server.reset()">Reset</button>"""# Called by onclickdefreset(self):# reset counter so elapsed time is 0self.start0=time.time()# executed on clientself.js.dom.time.innerHTML="{:.1f}".format(0)# If there is a "main" function, it gets executed. Program# ends when the function ends. If there is no main, then# server runs forever.defmain(self):# start counter so elapsed time is 0self.start0=time.time()whileTrue:# get current elapsed time, rounded to 0.1 secondst="{:.1f}".format(time.time()-self.start0)# update the DOM on the clientself.js.dom.time.innerHTML=ttime.sleep(0.1)httpd=Server(App)print("serving at port",httpd.port)httpd.start()Flask Example:<pid="time">TIME</p><buttonid="reset"onclick="server.reset()">Reset</button>importjyserver.FlaskasjsimporttimefromflaskimportFlask,render_template,requestapp=Flask(__name__)@js.use(app)classApp():defreset(self):self.start0=time.time()self.js.dom.time.innerHTML="{:.1f}".format(0)@js.taskdefmain(self):self.start0=time.time()whileTrue:t="{:.1f}".format(time.time()-self.start0)self.js.dom.time.innerHTML=ttime.sleep(0.1)@app.route('/')defindex_page(name=None):App.main()returnApp.render(render_template('flask-simple.html')Django examplefromdjango.shortcutsimportrenderimportjyserver.Djangoasjsimporttime@js.useclassApp():defreset(self):self.start0=time.time()self.js.dom.time.innerHTML="{:.1f}".format(0)@js.taskdefmain(self):self.start0=time.time()whileTrue:t="{:.1f}".format(time.time()-self.start0)self.js.dom.time.innerHTML=ttime.sleep(0.1)defhello_world(request):App.main()returnApp.render(render(request,'hello_world.html',{}))Inurls.pyadd this path:fromjyserver.Djangoimportprocess...url(r'^_process_srv0$',process,name='process'),Bottle exampleA Bottle application using the built-in server can only be single threaded and thus
all features may not work as expected. Most significantly, you cannot
evaluate Javascript expressions from server callbacks. This limitation
is not present if using a multi-threaded server such as tornado.frombottleimportroute,runimportjyserver.Bottleasjsimporttime@js.useclassApp():defreset(self):self.start0=time.time()@js.taskdefmain(self):self.start0=time.time()whileTrue:t="{:.1f}".format(time.time()-self.start0)self.js.dom.time.innerHTML=ttime.sleep(0.1)@route('/')defindex():html="""<p id="time">WHEN</p><button id="b1" onclick="server.reset()">Reset</button>"""App.main()returnApp.render(html)run(host='localhost',port=8080)InternalsHow does this work? In the standalone example, the process is below.
Flask/Bottle/Django is identical except for the httpd server.The server will listen for new http requests.When "/" is requested, jyserver will insert special Javascript code into the
HTML that enables communication before sending it to the browser. This code
creates theserverProxy object.This injected code will cause the browser to send an asynchronous http
request to the server asking for new commands for the browser to execute.
Then it waits for a response in the background. Requests are sent via
POST on /_process_srv0, which the server intercepts.When the user clicks on the buttonreset, theserverProxy object is
called. It will extract the method name--in this casereset--and then make
an http request to the server to execute that statement.The server will receive this http request, look at the App class, find a
method with that name and execute it.The executed methodreset()first increases the variablestart0. Then it
begins building a Javascript command by using the specialself.jscommand.self.jsuses Python's dynamic language features__getattr__,__setattr__, etc. to build Javascript syntax on the fly.When this "dynamic" statement get assigned a value (in our case"0.0"), it
will get converted to Javascript and sent to the browser, which has been
waiting for new commands in step 3. The statement will look like:document.getElementById("time").innerHTML = "0.0"The browser will get the statement, evaluate it and return the results to the
server. Then the browser will query for new commands in the background.It seems complicated but this process usually takes less than a 0.01 seconds. If
there are multiple statements to execute, they get queued and processed
together, which cuts back on the back-and-forth chatter.All communication is initiated by the browser. The server only listens for
special GET and POST requests.Overview of operationThe browser initiates all communcation. The server listens for connections and
sends respnses. Each page request is processed in its own thread so results may
finish out of order and any waiting does not stall either the browser or the
server.BrowserServerRequest pagesSend pages with injected JavascriptQuery for new commandsSend any queued commandsAs commands finish, send back resultsMatch results with commandsSend server statements for evaluation; wait for resultsExecutes then and sends back resultsWhen the browser queries for new commands, the server returns any pending
commands that the browser needs to execute. If there are no pending commands, it
waits for 5-10 seconds for new commands to queue before closing the connection.
The browser, upon getting an empty result will initiate a new connection to
query for results. Thus, although there is always a connection open between the
browser and server, this connection is reset every 5-10 seconds to avoid a
timeout.Other featuresAssign callables in Python.Functions are treated as first-class objects and can be assigned.classApp(Client):defstop(self):self.running=Falseself.js.dom.b2.onclick=self.restartdefrestart(self):self.running=Trueself.js.dom.b2.onclick=self.stopIf amainfunction is given, it is executed. When it finishes, the server is
terminated. If nomainfunction is given, the server waits for requests in an
infinite loop.Lazy evaluation provides live dataStatements are evaluated lazily byself.js. This means that they are executed
only when they are resolved to an actual value, which can cause some statements
to be evaluated out of order. For example, consider:v=self.js.var1self.js.var1=10print(v)This will always return10no matter whatvar1is initially. This is
because the assignmentv = self.js.var1assigns a Javascript object and not
the actual value. The object is sent to the browser to be evaluated only when
it is used by an operation. Every time you usevin an operation, it will be
sent to the browser for evaluation. In this way, it provides a live link to the
data.This behavior can be changed by callingv = self.js.var1.eval(), casting it
such asv = int(self.js.var)or performing some operation such as adding as inv = self.js.var + 10.InstallationAvailable using pip or condapipinstalljyserverSource code available ongithub:ftrias/jyserver