Examples

Quick start

A whole web application of Pyjoyment.Lite:

from Pyjoyment.Lite import *


get('/', {'text': 'I ♥ Pyjoyment!'})

app.start()

Duct tape for the HTML5 web

A WebSocket service used by the template to extract the title from a website:

"""
__DATA__

@@ index.html.ep
% url = url_for('title')
<script>
  const ws = new WebSocket('<%= url.to_abs() %>');
  ws.onmessage = function (event) { document.body.innerHTML += event.data };
  ws.onopen    = function (event) { ws.send('https://mojolicious.org') };
</script>
"""

from Pyjoyment.Lite import *


# Render template "index.html.ep" from the DATA section
@get('/')
def index(c):
    c.render(template='index')


# WebSocket service used by the template to extract the title from a website
@websocket('/title')
def title(c):
    @c.on('message')
    def message(c, msg):
        c.ua().get_p(msg).then(lambda tx: c.send(tx.result.dom().at('title').text))


app.start()

Applications of other frameworks

A WSGI application, in a file of its own, under a path of an application of Pyjoyment.Lite:

from Pyjoyment.Lite import *


# The WSGI application of hello_wsgi.py, next to this script, under /wsgi
plugin('Mount', {'/wsgi': 'hello_wsgi:app'})

get('/', {'text': 'I ♥ Pyjoyment!'})

app.start()

The WSGI application, which also runs on its own, on the WSGI server of the standard library:

from wsgiref.simple_server import make_server


def app(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return [f'Hello WSGI at {environ["SCRIPT_NAME"]} for {environ["PATH_INFO"]}!'.encode()]


if __name__ == '__main__':
    make_server('', 3000, app).serve_forever()

An application of Pyjoyment as the WSGI application, in the same file:

import Pyjo.Server.WSGI
import Pyjoyment

from Pyjoyment.Lite import *


# Hello world of Pyjoyment as a WSGI application, whose script name is the path it is mounted under
hello = Pyjoyment.new()
hello.routes.get('/').to(cb=lambda c: c.render(text=f'Hello Pyjoyment at {c.url_for("/")} on WSGI!'))
wsgi = Pyjo.Server.WSGI.new(app=hello).to_wsgi_app()

# The WSGI application under /wsgi of the application of Pyjoyment.Lite
plugin('Mount', {'/wsgi': wsgi})

get('/', {'text': 'I ♥ Pyjoyment!'})

# The application of a WSGI server too, which PYJO_WSGI tells: PYJO_WSGI=1 gunicorn mount_wsgi_inception:application
application = app.start()

An ASGI application, in a file of its own, under a path of an application of Pyjoyment.Lite, whose events stream at the pace of the client:

from Pyjoyment.Lite import *


# The ASGI application of hello_asgi.py, next to this script, under /asgi
plugin('Mount', {'/asgi': 'hello_asgi:app'})

get('/', {'text': 'I ♥ Pyjoyment!'})

app.start()

The ASGI application, which also runs on its own, on an ASGI server:

# On an ASGI server: uvicorn hello_asgi:app
async def app(scope, receive, send):
    if scope['type'] != 'http':
        return

    # Server-sent events, each sent once the one before has been written
    if scope['path'].endswith('/events'):
        await send({'type': 'http.response.start', 'status': 200, 'headers': [(b'content-type', b'text/event-stream')]})
        for n in range(3):
            await send({'type': 'http.response.body', 'body': f'data: {n}\n\n'.encode(), 'more_body': True})
        await send({'type': 'http.response.body', 'body': b''})
        return

    body = f'Hello ASGI at {scope["root_path"]} for {scope["path"]}!'.encode()
    await send({'type': 'http.response.start', 'status': 200, 'headers': [(b'content-type', b'text/plain')]})
    await send({'type': 'http.response.body', 'body': body})

An application of Pyjoyment as the ASGI application, in the same file:

import Pyjo.Server.ASGI
import Pyjoyment

from Pyjoyment.Lite import *


# Hello world of Pyjoyment as an ASGI application, whose root path is the path it is mounted under
hello = Pyjoyment.new()
hello.routes.get('/').to(cb=lambda c: c.render(text=f'Hello Pyjoyment at {c.url_for("/")} on ASGI!'))
asgi = Pyjo.Server.ASGI.new(app=hello).to_asgi_app()

# The ASGI application under /asgi of the application of Pyjoyment.Lite
plugin('Mount', {'/asgi': asgi})

get('/', {'text': 'I ♥ Pyjoyment!'})

# The application of an ASGI server too, which PYJO_ASGI tells: PYJO_ASGI=1 uvicorn mount_asgi_inception:application
application = app.start()

PHP on php-fpm, a FastCGI application, under a path of an application of Pyjoyment.Lite, with the scripts of its directory and the pool of php-fpm:

import Pyjo.App.FastCGI

from Pyjo.File import curfile
from Pyjoyment.Lite import *


# The scripts of PHP of the directory php under /php, on php-fpm with the pool of php/php-fpm.conf next to this script:
# php-fpm -n -y php/php-fpm.conf -F
fastcgi = Pyjo.App.FastCGI.new(url='fcgi://127.0.0.1:9000', document_root=curfile().sibling('php').to_str())
plugin('Mount', {'/php': fastcgi})

get('/', {'text': 'I ♥ Pyjoyment!'})

app.start()
<?php
// The script of the directory, and of every path under the mount when it is the only one, as a front controller is
if ($_SERVER['PATH_INFO'] === '/events') {
    header('Content-Type: text/event-stream');
    for ($n = 0; $n < 3; $n++) {
        echo "data: $n\n\n";
        flush();
    }
    exit;
}

header('Content-Type: text/plain');
echo "Hello PHP at {$_SERVER['SCRIPT_NAME']} for {$_SERVER['PATH_INFO']} ({$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']})!\n";
<?php phpinfo(); ?>
; A pool of php-fpm in the foreground, for mount_fastcgi_php.py: php-fpm -n -y php/php-fpm.conf -F
[global]
error_log = /dev/stderr
daemonize = no

[www]
listen = 127.0.0.1:9000
pm = static
pm.max_children = 2

A CGI program of Python in the directory cgi-bin, under a path of an application of Pyjoyment.Lite, a process for each request:

import Pyjo.App.CGI

from Pyjo.File import curfile
from Pyjoyment.Lite import *


# The programs of the directory cgi-bin, next to this script, under /cgi-bin, a process for each request
plugin('Mount', {'/cgi-bin': Pyjo.App.CGI.new(document_root=curfile().sibling('cgi-bin').to_str())})

get('/', {'text': 'I ♥ Pyjoyment!'})

app.start()
#!/usr/bin/env python3
# A CGI program, which writes its response: the headers, an empty line and the body
import os
import urllib.parse


name = urllib.parse.parse_qs(os.environ.get('QUERY_STRING', '')).get('name', ['CGI'])[0]
print('Content-Type: text/plain; charset=UTF-8')
print()
print(f'Hello {name} at {os.environ["SCRIPT_NAME"]} for {os.environ["PATH_INFO"]}!')

PHP on php-cgi, with the scripts of the same directory as for php-fpm, a process of php-cgi for each request:

import Pyjo.App.CGI

from Pyjo.File import curfile
from Pyjoyment.Lite import *


# The scripts of PHP of the directory php under /php, on php-cgi of PATH, a process for each request
cgi = Pyjo.App.CGI.new(command=['php-cgi'], document_root=curfile().sibling('php').to_str(), script_extensions=['.php'])
plugin('Mount', {'/php': cgi})

get('/', {'text': 'I ♥ Pyjoyment!'})

app.start()

Servers

Pyjo.HelloWorld natively, on the web server of Pyjoyment:

import Pyjo.HelloWorld
import Pyjo.Server.Daemon


Pyjo.Server.Daemon.new(app=Pyjo.HelloWorld.new(), listen=['http://*:3000']).run()

On uvicorn, an ASGI server:

import Pyjo.HelloWorld
import Pyjo.Server.ASGI

import uvicorn


app = Pyjo.Server.ASGI.new(app=Pyjo.HelloWorld.new()).to_asgi_app()

uvicorn.run(app, port=3000)

On Werkzeug, a WSGI server:

import Pyjo.HelloWorld
import Pyjo.Server.WSGI

from werkzeug.serving import run_simple


app = Pyjo.Server.WSGI.new(app=Pyjo.HelloWorld.new()).to_wsgi_app()

run_simple('localhost', 3000, app)

As an event function of AWS Lambda:

import Pyjo.HelloWorld
import Pyjo.Server.Lambda


# The handler "hello_world_lambda.handler" of an event function of AWS Lambda
handler = Pyjo.Server.Lambda.new(app=Pyjo.HelloWorld.new()).to_lambda_app()

Requests on a timer

The user agent requests the server every second, in the same event loop, without blocking it:

import Pyjo.HelloWorld
import Pyjo.IOLoop
import Pyjo.Server.Daemon
import Pyjo.UserAgent


# The server and the user agent, in the same event loop, which close their sockets at the end of the block
with (
    Pyjo.Server.Daemon.new(app=Pyjo.HelloWorld.new(), listen=['http://*:3000']) as daemon,
    Pyjo.UserAgent.new() as ua,
):
    # A request every second, which does not block the event loop; a request that the end of the block interrupts gets
    # an error
    @Pyjo.IOLoop.recurring(1)
    def fetch(loop):
        def done(ua, tx):
            print(tx.error['message'] if tx.error else f'{tx.res.code} {tx.res.text}')

        ua.get('http://127.0.0.1:3000/', done)

    # The server runs the event loop until INT or TERM stops it
    daemon.run()

A request on a loopback

A blocking request of the user agent to the server, in the same event loop:

import Pyjo.HelloWorld
import Pyjo.IOLoop
import Pyjo.Server.Daemon
import Pyjo.UserAgent


# The server, on a port that the system picks, and the user agent, in the same event loop, which close their sockets at
# the end of the block
with (
    Pyjo.Server.Daemon.new(app=Pyjo.HelloWorld.new(), listen=['http://127.0.0.1'], silent=True) as daemon,
    Pyjo.UserAgent.new(ioloop=Pyjo.IOLoop.singleton) as ua,
):
    # A blocking request, which runs the event loop of the server until the response
    tx = ua.get(f'http://127.0.0.1:{daemon.start().ports()[0]}/')
    print(tx.result.code, tx.result.text)

Web scraping

import Pyjo.UserAgent

from Pyjo.String.Unicode import u


# Extract named character references from HTML spec
tx = Pyjo.UserAgent.new().get('https://html.spec.whatwg.org')
for n in tx.res.dom('#named-character-references-table tbody > tr').each():
    u(n.at('td > code').text + ' ' + n.children('td')[1].text).trim().say()

URL manipulation

import Pyjo.URL


# 'ssh+git://git@github.com/dex4er/Pyjoyment.git'
url = Pyjo.URL.new('https://github.com/dex4er/Pyjoyment')
print(url.set(scheme='ssh+git', userinfo='git', path=f'{url.path}.git').to_unsafe_str())

# 'https://pypi.org/search/?q=Pyjoyment&o=&c=Programming+Language+%3A%3A+Python'
url = Pyjo.URL.new('https://pypi.org/search/')
print(url.set(query=[('q', 'Pyjoyment'), ('o', ''), ('c', 'Programming Language :: Python')]))

Non-blocking TCP client/server

import Pyjo.IOLoop


# Listen on port 3000
@Pyjo.IOLoop.server(port=3000)
def server(loop, stream, cid):

    @stream.on
    def read(stream, chunk):
        # Process input chunk
        print(f'Server: {chunk.decode("utf-8")}')

        # Write response
        stream.write(b'HTTP/1.1 200 OK\x0d\x0a\x0d\x0a')

        # Disconnect client
        stream.close_gracefully()


# Connect to port 3000
@Pyjo.IOLoop.client(port=3000)
def client(loop, err, stream):

    @stream.on
    def read(stream, chunk):
        # Process input
        print(f'Client: {chunk.decode("utf-8")}')

    # Write request
    stream.write(b'GET / HTTP/1.1\x0d\x0a\x0d\x0a')


# Add a timer
@Pyjo.IOLoop.timer(3)
def timeouter(loop):
    print('Timeout')
    # Shutdown server
    loop.remove(server)


# Start event loop
Pyjo.IOLoop.start()

Standalone HTTP server serving embedded template file

"""
__DATA__

@@ index.html.ep
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Pyjoyment</title>
</head>
<body>
<h1>♥ Pyjoyment ♥</h1>
<h2>This page is served by Pyjoyment framework.</h2>
<p><%= method %> request for <%= path %></p>
</body>
</html>
"""

import Pyjo.App
import Pyjo.GetOpt
import Pyjo.Server.Daemon
import Pyjo.Template
import Pyjo.URL

from Pyjo.Loader import data_section
from Pyjo.Util import die


class MyApp(Pyjo.App.object):
    # Embedded template file
    template = Pyjo.Template.new(auto_escape=True, vars=True).parse(data_section(__name__, 'index.html.ep'))

    # All the complexities of CGI, WSGI, ASGI, HTTP and WebSockets get reduced to a single method call!
    def handler(self, tx):
        # Resume transaction at the end of the block
        with tx:
            # Request
            method = tx.req.method
            path = tx.req.url.path

            # Response
            tx.res.code = 200
            tx.res.headers.content_type = 'text/html; charset=utf-8'
            tx.res.body = self.template.process({'method': method, 'path': path}).encode()


# Options --address and --port
getopt = Pyjo.GetOpt.new().option('a|address=s', default='0.0.0.0').option('p|port=i', default=3000)
opts = getopt.parse() or die('Usage: http_server.py [--address ADDRESS] [--port PORT]')

url = Pyjo.URL.new().set(scheme='http', host=opts.address, port=opts.port)

Pyjo.Server.Daemon.new(app=MyApp.new(), listen=[url]).run()