Getting Started

Installation

FeretUI needs some dependencies system:

sudo npm install -g less

Install released versions of FeretUI from the Python package index with pip or a similar tool:

pip install feretui (Not ready yet)

Installation via source distribution is via the pyproject.toml script:

pip install .

Installation will add the feretui commands to the environment.

Note

FeretUI use Python version >= 3.10

Install your favorite web server

FeretUI come without any web server. The web serving is not this job.

The routes to serve FeretUI

We need 3 routes :

  • [GET] client route

  • [GET] static files route

  • [GET, POST, PATCH, PUT, DELETE] action route.

Note

The GET, POST, DELETE is the minimum method to use the actions. The other is to add only if you need custom form with theses methods.

Request, Response and Session

The objects Request, Response and Session are wrapper class. The goal is to séparate and link the three object come from web server to FeretUI.

FeretUI can not know all the web server and can not be adapted for each. We need a link between them. The responsability to do the link is yours.

Some exemple exist and can copy it or adapt it for your projects.

Client route

The client route is the route that return the FeretUI client. the route (name and url) is free.

The route should

session = ...  # get the feretui session
myferet = ...  # get the instance client
request = Request(session=session, ...)  # get the feretui request
response = myferet.render(request)  # get the client page
# return the response formated for the web server

static files route

The static files route is the route that return the javascript, css, font, images.

the url is /feretui base url/static/filepath

The route should

filepath = ...  # get the file path
myferet = ...  # get the instance client
return myferet.get_static_file_path(filepath)  # return the static file

Action route

The action route is used to do action on and with the FeretUI client.

  • render page

  • execute a page

the url is /feretui base url/static/action

The route should

action = ...  # get the action to call
session = ...  # get the feretui session
myferet = ...  # get the instance client
request = Request(session=session, ...)  # get the feretui request
response = myferet.execute_action(request, action)  # execute the action
# return the response formated for the web server

Serve FeretUI with bottle

Bottle is a fast, simple and lightweight WSGI micro web-framework for Python. It is distributed as a single file module and has no dependencies other than the Python Standard Library.

See the bottle documentation.

For this example you need to install some additional package

pip install bottle BottleSessions
import logging
from contextlib import contextmanager
from os import path

from bottle import abort, app, request, response, route, run, static_file
from BottleSessions import BottleSessions
from multidict import MultiDict

from feretui import FeretUI, Request, Session

logging.basicConfig(level=logging.DEBUG)


@contextmanager
def feretui_session(cls):
    session = None
    try:
        session = cls(**request.session)
        yield session
    finally:
        if session:
            request.session.update(session.to_dict())


def add_response_headers(headers) -> None:
    for k, v in headers.items():
        response.set_header(k, v)


myferet = FeretUI()

# Here define your feretui stuff.


@route('/')
def index():
    with feretui_session(Session) as session:
        frequest = Request(
            method=Request.GET,
            querystring=request.query_string,
            headers=dict(request.headers),
            session=session,
        )
        res = myferet.render(frequest)
        add_response_headers(res.headers)
        return res.body


@route('/feretui/static/<filepath:path>')
def feretui_static_file(filepath):
    filepath = myferet.get_static_file_path(filepath)
    if filepath:
        root, name = path.split(filepath)
        return static_file(name, root)

    abort(404)


@route('/feretui/action/<action>', method=['DELETE', 'GET', 'POST'])
def call_action(action):
    with feretui_session(Session) as session:
        frequest = Request(
            method=getattr(Request, request.method),
            querystring=request.query_string,
            form=MultiDict(request.forms),
            params=MultiDict(request.params),
            headers=dict(request.headers),
            session=session,
        )
        res = myferet.execute_action(frequest, action)
        add_response_headers(res.headers)
        return res.body


if __name__ == "__main__":
    app = app()
    cache_config = {
        'cache_type': 'FileSystem',
        'cache_dir': './sess_dir',
        'threshold': 2000,
    }
    BottleSessions(
        app, session_backing=cache_config, session_cookie='appcookie')
    run(host="localhost", port=8080, debug=True, reloader=1)

Serve FeretUI with flask

Flask is a lightweight WSGI web application framework. It is designed to make getting started quick and easy, with the ability to scale up to complex applications. It began as a simple wrapper around Werkzeug and Jinja, and has become one of the most popular Python web application frameworks.

See the flask documentation.

For this example you need to install some additional package

pip install flask
import logging
import urllib
from contextlib import contextmanager
from wsgiref.simple_server import make_server

from flask import Flask, abort, make_response, request, send_file
from multidict import MultiDict

from feretui import FeretUI, Request, Session

logging.basicConfig(level=logging.DEBUG)

app = Flask(__name__)
app.secret_key = b'secret'


@contextmanager
def feretui_session(cls):
    from flask import session
    fsession = None
    try:
        fsession = cls(**session)
        yield fsession
    finally:
        if fsession:
            session.update(fsession.to_dict())


def response(fresponse):
    resp = make_response(fresponse.body)
    resp.headers.update(fresponse.headers)
    return resp


myferet = FeretUI()

# Here define your feretui stuff.


@app.route('/')
def index():
    with feretui_session(Session) as session:
        frequest = Request(
            method=Request.GET,
            querystring=request.query_string.decode('utf-8'),
            headers=dict(request.headers),
            session=session,
        )
        return response(myferet.render(frequest))


@app.route('/feretui/static/<path:filepath>')
def feretui_static_file(filepath):
    filepath = myferet.get_static_file_path(filepath)
    if filepath:
        return send_file(filepath)

    abort(404)
    return None


@app.route('/feretui/action/<action>', methods=['DELETE', 'GET', 'POST'])
def call_action(action):
    params = {}
    if request.method in ['DELETE', 'POST']:
        params = {
            x: request.form.getlist(x)
            for x in request.form
        }
        params.update(urllib.parse.parse_qs(
            request.query_string.decode('utf-8'),
        ))

    with feretui_session(Session) as session:
        frequest = Request(
            method=getattr(Request, request.method),
            querystring=request.query_string.decode('utf-8'),
            form=MultiDict(request.form),
            params=params,
            headers=dict(request.headers),
            session=session,
        )
        return response(myferet.execute_action(frequest, action))


if __name__ == "__main__":
    with make_server('', 8080, app) as httpd:
        logging.info("Serving on port 8080...")
        httpd.serve_forever()

Serve FeretUI with pyramid

Pyramid is a small, fast, down-to-earth, open source Python web framework. It makes real-world web application development and deployment more fun, more predictable, and more productive. Try Pyramid, browse its add-ons and documentation, and get an overview.

See the pyramid documentation.

For this example you need to install some additional package

pip install pyramid, pyramid_beaker
import logging
from contextlib import contextmanager
from wsgiref.simple_server import make_server

from multidict import MultiDict
from pyramid.config import Configurator
from pyramid.httpexceptions import exception_response
from pyramid.response import FileResponse, Response
from pyramid.view import view_config
from pyramid_beaker import session_factory_from_settings

from feretui import FeretUI, Request, Session

logging.basicConfig(level=logging.DEBUG)


@contextmanager
def feretui_session(feretui_session_cls, pyramid_session):
    fsession = None
    try:
        fsession = feretui_session_cls(**pyramid_session)
        yield fsession
    finally:
        if fsession:
            pyramid_session.update(fsession.to_dict())
            pyramid_session.save()


myferet = FeretUI()

# Here define your feretui stuff.


@view_config(route_name='feretui', request_method='GET')
def feretui(request):
    with feretui_session(Session, request.session) as session:
        frequest = Request(
            method=Request.GET,
            querystring=request.query_string,
            headers=dict(request.headers),
            session=session,
        )
        response = myferet.render(frequest)
        return Response(
            response.body,
            headers=response.headers,
        )


@view_config(route_name='feretui_static_file', request_method='GET')
def feretui_static_file(request):
    filepath = myferet.get_static_file_path(
        '/'.join(request.matchdict['filepath']),
    )
    if filepath:
        return FileResponse(filepath)

    raise exception_response(404)


@view_config(
    route_name='call_action',
    request_method=('DELETE', 'GET', 'POST'),
)
def call_action(request):
    action = request.matchdict['action']
    with feretui_session(Session, request.session) as session:
        frequest = Request(
            method=getattr(Request, request.method),
            querystring=request.query_string,
            form=MultiDict(request.POST),
            params=request.params.dict_of_lists(),
            headers=dict(request.headers),
            session=session,
        )
        response = myferet.execute_action(frequest, action)
        return Response(
            response.body,
            headers=response.headers,
        )


if __name__ == "__main__":
    session_factory = session_factory_from_settings({})
    with Configurator() as config:
        config.include('pyramid_beaker')
        config.set_session_factory(session_factory)
        config.add_route('feretui', '/')
        config.add_route('feretui_static_file', '/feretui/static/*filepath')
        config.add_route('call_action', '/feretui/action/{action}')
        config.scan()
        app = config.make_wsgi_app()

    with make_server('', 8080, app) as httpd:
        logging.info("Serving on port 8080...")
        httpd.serve_forever()

Install your favorite ORM

FeretUI come without any ORM. It is not this job, It not required to have an ORM. You can do without directly with SQL or just with full static page.

Defined the content of your application