Chapter 4: Le coeur

Le coeur

Options de ligne de commande

Il est possible de démarrer web2py directement sans utiliser l'interface graphique depuis la ligne de commande en tapant quelque chose comme :

password
python web2py.py -a 'your password' -i 127.0.0.1 -p 8000

Lorsque web2py démarre, il créé un fichier appelé "parameters_8000.py" où il stocke le mot de passe sous forme de hash. Si vous utilisez "<ask>" comme mot de passe, web2py vous le demandera.

Pour plus de sécurité, vous pouvez démarrer web2py avec :

python web2py.py -a '<recycle>' -i 127.0.0.1 -p 8000

Dans ce cas, web2py ré-utilise le précédent mot de passe en hash stocké. Si aucun mot de passe n'est fourni, ou si le fichier "parameters_8000.py" est supprimé, l'interface web d'administration est désactivée.

PAM

Sur certains systèmes Unix/Linux, si le mot de passe est

<pam_user:some_user>

web2py utilise le mot de passe PAM du compte sur le système d'exploitation de some_user pour authentifier l'administrateur, à moins qu'il ne soit bloqué par la configuration PAM.

web2py s'exécute normalement avec CPython (l'implémentation C de l'interpréteur Python créée par Guido van Rossum), mais il peut également être exécuté avec PyPy et Jython. La dernière possibilité permet l'utilisation de web2py dans un contexte d'infrastructure de Java EE. Pour utiliser Jython, remplacez simplement "python web2py.py ..." avec "jython web2py.py". Les détails à propos de l'installation de Jython, des modules zxJDBC requis pour accéder aux bases de données peuvent être trouvées dans le chapitre 14.

Le script "web2py.py" peut prendre plusieurs arguments en ligne de commande pour spécifier le nombre maximum de threads, activer le SSL, etc. Pour une liste complète :

command line
>>> python web2py.py -h
Usage: python web2py.py

web2py Web Framework startup script. ATTENTION: unless a password
is specified (-a 'passwd'), web2py will attempt to run a GUI.
In this case command line options are ignored.

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -i IP, --ip=IP        IP address of the server (e.g., 127.0.0.1 or ::1);
                        Note: This value is ignored when using the
                        'interfaces' option.
  -p PORT, --port=PORT  port of server (8000)
  -a PASSWORD, --password=PASSWORD
                        password to be used for administration (use -a
                        "<recycle>" to reuse the last password))
  -c SSL_CERTIFICATE, --ssl_certificate=SSL_CERTIFICATE
                        file that contains ssl certificate
  -k SSL_PRIVATE_KEY, --ssl_private_key=SSL_PRIVATE_KEY
                        file that contains ssl private key
  --ca-cert=SSL_CA_CERTIFICATE
                        Use this file containing the CA certificate to
                        validate X509 certificates from clients
  -d PID_FILENAME, --pid_filename=PID_FILENAME
                        file to store the pid of the server
  -l LOG_FILENAME, --log_filename=LOG_FILENAME
                        file to log connections
  -n NUMTHREADS, --numthreads=NUMTHREADS
                        number of threads (deprecated)
  --minthreads=MINTHREADS
                        minimum number of server threads
  --maxthreads=MAXTHREADS
                        maximum number of server threads
  -s SERVER_NAME, --server_name=SERVER_NAME
                        server name for the web server
  -q REQUEST_QUEUE_SIZE, --request_queue_size=REQUEST_QUEUE_SIZE
                        max number of queued requests when server unavailable
  -o TIMEOUT, --timeout=TIMEOUT
                        timeout for individual request (10 seconds)
  -z SHUTDOWN_TIMEOUT, --shutdown_timeout=SHUTDOWN_TIMEOUT
                        timeout on shutdown of server (5 seconds)
  --socket-timeout=SOCKET_TIMEOUT
                        timeout for socket (5 second)
  -f FOLDER, --folder=FOLDER
                        location of the applications folder (also known as directory) 
  -v, --verbose         increase --test verbosity
  -Q, --quiet           disable all output
  -D DEBUGLEVEL, --debug=DEBUGLEVEL
                        set debug output level (0-100, 0 means all, 100 means
                        none; default is 30)
  -S APPNAME, --shell=APPNAME
                        run web2py in interactive shell or IPython (if
                        installed) with specified appname (if app does not
                        exist it will be created). APPNAME like a/c/f (c,f
                        optional)
  -B, --bpython         run web2py in interactive shell or bpython (if
                        installed) with specified appname (if app does not
                        exist it will be created). Use combined with --shell
  -P, --plain           only use plain python shell; should be used with
                        --shell option
  -M, --import_models   auto import model files; default is False; should be
                        used with --shell option
  -R PYTHON_FILE, --run=PYTHON_FILE
                        run PYTHON_FILE in web2py environment; should be used
                        with --shell option
  -K SCHEDULER, --scheduler=SCHEDULER
                        run scheduled tasks for the specified apps: expects a
                        list of app names as -K app1,app2,app3 or a list of
                        app:groups as -K app1:group1:group2,app2:group1 to
                        override specific group_names. (only strings, no
                        spaces allowed. Requires a scheduler defined in the
                        models
  -X, --with-scheduler  run schedulers alongside webserver
  -T TEST_PATH, --test=TEST_PATH
                        run doctests in web2py environment; TEST_PATH like
                        a/c/f (c,f optional)
  -C, --cron            trigger a cron run manually; usually invoked from a
                        system crontab
  --softcron            triggers the use of softcron
  -Y, --run-cron        start the background cron process
  -J, --cronjob         identify cron-initiated command
  -L CONFIG, --config=CONFIG
                        config file
  -F PROFILER_FILENAME, --profiler=PROFILER_FILENAME
                        profiler filename
  -t, --taskbar         use web2py gui and run in taskbar (system tray)
  --nogui               text-only, no GUI
  -A ARGS, --args=ARGS  should be followed by a list of arguments to be passed
                        to script, to be used with -S, -A must be the last
                        option
  --no-banner           Do not print header banner
  --interfaces=INTERFACES
                        listen on multiple addresses: "ip1:port1:key1:cert1:ca
                        _cert1;ip2:port2:key2:cert2:ca_cert2;..."
                        (:key:cert:ca_cert optional; no spaces; IPv6 addresses
                        must be in square [] brackets)
  --run_system_tests    runs web2py tests

Notez bien : L'option -W, utilisée pour installer un service Windows, a été supprimée. used to install a Windows service, has been removed. Regardez svp nssm dans le chapitre Possibilités de déploiement

Les options en minuscules sont utilisées pour configurer le serveur web. L'option -L demande à web2py de lire les options de configuration depuis un fichier, -W installe web2py comme service windows, pendant que les options -S, -P et -M démarrent un shell interactif Python. L'option -T trouve et exécute le contrôleur doctests dans un environnement d'exécution web2py. Par exemple, le cas suivant exécute doctests depuis tous les contrôleurs de l'application "welcome" :

python web2py.py -vT welcome

si vous exécutez web2py comme service Windows, -W, il n'est pas pratique de passer la configuration en utilisant les arguments de la ligne de commande. Pour cette raison, vous trouverez dans dossier web2py un dossier avec un exemple de fichier de configuration pour le serveur web interne "options_std.py" :

import socket
import os

ip = '0.0.0.0'
port = 80
interfaces = [('0.0.0.0', 80)]
               #,('0.0.0.0',443,'ssl_private_key.pem','ssl_certificate.pem')]
password = '<recycle>'  # ## <recycle> means use the previous password
pid_filename = 'httpserver.pid'
log_filename = 'httpserver.log'
profiler_filename = None
ssl_certificate = None  # 'ssl_certificate.pem'  # ## path to certificate file
ssl_private_key = None  # 'ssl_private_key.pem'  # ## path to private key file
#numthreads = 50 # ## deprecated; remove
minthreads = None
maxthreads = None
server_name = socket.gethostname()
request_queue_size = 5
timeout = 30
shutdown_timeout = 5
folder = os.getcwd()
extcron = None
nocron = None

Ce fichier contient les valeurs par défaut web2py. Si vous éditez ce fichier, vous avez besoin de l'importer explicitement avec l'option -L en ligne de commande. Cela fonctionne uniquement si vous démarrez web2py en tant que service Windows.

Workflow

Le workflow web2py est le suivant :

  • Une requête HTTP arrive au serveur web (le serveur pré-packagé Rocket ou un serveur différent connecté à web2py via WSGI ou un autre adapteur). Le serveur web gère chaque requête dans son propre thread, en parallèle.
  • L'en-tête de la requête HTTP est parsée et passée au répartiteur (expliqué plus tart dans ce chapitre).
  • Le répartiteur décide laquelle des applications installées va gérer la requête et mappe le PATH_INFO dans l'URL en un appel de fonction. Chaque URL correspond à un appel de fonction.
  • Les requêtes pour les fichiers dans le dossier static sont gérés directement, et les gros fichiers sont automatiquement transmis en flux au client.
  • Les requêtes pour quelconque fichier sauf un fichier statique sont mappées dans une action (i.e. une fonction dans un fichier contrôleur, dans l'application demandée).
  • Avant d'appeler l'action, certaines choses arrivent : si l'en-tête de la requête contient un cookie de session pour l'application, l'objet de session est récupéré ; s'il n'y en a pas, un id de session est créé (mais le fichier de session n'est pas conservé pour plus part) ; un environnement d'exécution pour la requête est créé ; les modèles sont exécutés dans cet environnement.
  • Finalement, l'action du contrôleur est exécutée dans l'environnement pré-compilé.
  • Si l'action retourne une chaîne de caractères, elle est retournée au client (ou si l'action retourne un objet HTML Helper Web2py, il est sérialisé et retourné au client).
  • Si l'action retourne un itérable, il est utilisé pour boucler et envoie les données en flux au client.
  • Si l'action retourne un dictionnaire, web2py essaie de localiser une vue pour rendre le dictionnaire. La vue doit avoir le même nom que l'action (à moins d'une spécification différente) et la même extension que la page demandée (par défaut .html) ; en cas d'échec, web2py peut récupérer une vue générique (si disponible et activée). La vue voit toute variable définie dans les modèles aussi bien que celles du dictionnaire retourné par l'action, mais ne voit pas les variables globales définies dans le contrôleur.
  • Le code utilisateur complet est exécuté dans une transaction de base de données simple sauf si spécifié autrement.
  • Si le code utilisateur réussit, la transaction est validée.
  • Si le code utilisateur échoue, la traceback est stockée dans un ticket, et un ID de ticket est donné au client. Seul l'administrateur système peut rechercher et lire les tracebacks dans les tickets.

Il y a quelques avertissements à garder en tête :

  • Les modèles dans le même dossier/sous-dossier sont exécutés par ordre alphabétique
  • Quelconque variable définie dans un modèle sera visible par les autres modèles alphabétiquement, par les contrôleurs, et par les vues.
models_to_run
conditional models

  • Les modèles dans les sous-dossiers sont exécutés sous condition. Par exemple, si l'utilisateur a demandé "/a/c/f" où "a" est l'application, "c" est le contrôleur et "f" est la fonction (action), alors les modèles suivants seront exécutés :
applications/a/models/*.py
applications/a/models/c/*.py
applications/a/models/c/f/*.py
Ce comportement est renforcé par défaut. En modifiant la liste regex de response.models_to_run, vous pouvez forcer le comportement que vous voulez. Regardez response pour de plus amples détails
  • Le contrôleur demandé est exécuté et la fonction demandée est appelée. Cela signifie que tout code haut-niveau dans le contrôleur est également exécuté à chaque requête pour ce contrôleur.
  • La vue est uniquement appelée si l'action retourne un dictionnaire.
  • Si une vue n'est pas trouvée, web2py essaie d'utiliser une vue générique. Par défaut, les vues génériques sont désactivées, bien que l'application 'welcome' inclut une ligne dans /models/db.py pour les activer en localhost uniquement. Elles peuvent être activées par type d'extension et par action (en utilisant response.generic_patterns). En général, les vues génériques sont un outil de développement et ne devraient typiquement pas être utilisées en production. Si vous souhaitez que certaines actions utilisent une vue générique, listez ces actions dans response.generic_patterns (discuté plus en détail dans le chapitre sur les Services).

Les comportements possibles d'une action sont les suivants :

Renvoyer une chaîne de caractères :

def index(): return 'data'

Renvoyer un dictionnaire pour une vue :

def index(): return dict(key='value')

Retourner toutes les variables locales :

def index(): return locals()

Rediriger l'utilisateur vers une autre page :

def index(): redirect(URL('other_action'))

Retourner une autre page HTTP que "200 OK" :

def index(): raise HTTP(404)

Retourner un helper (par exemple, un FORM) :

def index(): return FORM(INPUT(_name='test'))

(celui-ci est principalement utilisé pour les callbacks et les composants Ajax, voir le chapitre 12)

Lorsqu'une action retourne un dictionnaire, il peut contenir du code généré par les helpers, incluant les formulaires basés sur les tables de base de données depuis un factory, par exemple :

def index(): return dict(form=SQLFORM.factory(Field('name')).process())

(tous les formulaires générés par web2py utilisent les postbacks, voir chapitre 3)

Répartition

url mapping
dispatching

web2py mappe une URL du formulaire :

http://127.0.0.1:8000/a/c/f.html

vers la fonction f() dans le contrôleur "c.py" dans l'application "a". Si f n'est pas présent, web2py renvoie par défaut vers la fonction index du contrôleur. Si c n'est pas présent, web2py renvoie par défaut vers le contrôleur "default.py", et si a n'est pas présent, web2py renvoie par défaut vers l'application init. S'il n'y a pas d'application init, web2py essaie d'exécuter l'application welcome. Ceci est montré de manière schématique dans l'image suivante :

image

Par défaut, toute nouvelle requête créé aussi une nouvelle session. En supplément, un cookie de session est renvoyé au navigateur pour conserver la session.

L'extension .html est optionnelle ; .html est défini par défaut. L'extension détermine l'extension de la vue qui renvoie la sortie de la fonction f() du contrôleur. Il permet au même contenu d'être fourni dans de nombreux formats (html, xml, json, rss, etc.).

Les fonctions qui prennent des arguments ou démarrent avec un double underscore ne sont pas publiquement exposées et peuvent uniquement être appelées par les autres fonctions.

static files

Il y a une exception faite pour les URLs du formulaire :

http://127.0.0.1:8000/a/static/filename

Il n'y a pas de contrôleur appelé "static". web2py interprète ceci comme une requête pour le fichier appelé "filename" dans le sous-dossier "static" de l'application "a".

PARTIAL CONTENT
IF_MODIFIED_SINCE
Lorsque des fichiers statiques sont téléchargés, web2py ne créé pas une session, ne créé pas de cookie ou exécute le modèle. web2py envoie toujours les fichiers statiques en flux par morceaux de 1Mo, et envoie PARTIAL CONTENT quand le client envoie une requête RANGE sur un sous-ensemble de fichier.

we2py supporte également le protocole IF_MODIFIED_SINCE, et n'envoie pas le fichier s'il est déjà stocké dans le cache du navigateur et si le fichier n'a pas changé depuis cette version.

Lorsque vous liez un fichier audio ou vidéo dans le dossier static, si vous souhaitez forcer le navigateur à télécharger le fichier au lieu d'effectuer du streaming via un lecteur, ajoutez ?attachment à l'URL. Ceci indiquera à web2py de mettre l'en-tête HTTP Content-Disposition de la réponse HTTP à "attachment". Par exemple :

<a href="/app/static/my_audio_file.mp3?attachment">Download</a>

Lorsque le lien ci-dessus est cliqué, le navigateur va proposer à l'utilisateur de télécharger le fichier mp3 plutôt que d'effectuer du streaming avec l'audio. (Comme discuté ci-dessous, vous pouvez également mettre les en-têtes HTTP pour la réponse directement en définissant un dict des noms d'en-têtes et leurs valeurs à response.headers.)

request.application
request.controller
request.function
GET
POST
request.args
web2py mappe les requêtes GET/POST du formulaire :

http://127.0.0.1:8000/a/c/f.html/x/y/z?p=1&q=2

à la fonction f dans le contrôleur "c.py" dans l'application a, et il stocke les paramètres de l'URL dans la variable request comme suit :

request.args = ['x', 'y', 'z']

et :

request.vars = {'p':1, 'q':2}

et :

request.application = 'a'
request.controller = 'c'
request.function = 'f'

Dans l'exemple ci-dessus, request.args[i] et request.args(i) peuvent être utilisés pour retrouver le i-ème élément de request.args, mais alors que le premier lève une exception si la liste n'a pas de tel index, le dernier retourne None dans ce cas.

request.url
request.url

stocke l'URL complète de la requête courante (sans inclure les variables GET).

request.ajax
request.cid

request.ajax

par défaut False mais devient True si web2py détermine que l'action appelée a été appelée par une requête Ajax.

Si la requête est une requête Ajax et qu'elle est initiée par un composant web2py, le nom du composant peut être trouvé dans :

request.cid

Les composants sont présentés plus en détail dans le chapitre 12.

request.get_vars
request.post_vars
request.vars
Si la requête HTTP est une requête GET, alors request.env.request_method est mis à "GET" ; si c'est une requête POST, request.env.request_method est mis à "POST". Les variables d'URL de la requête sont stockées dans request.get_vars. request.post_vars contient tous les paramètres passés dans le corps d'une requête (habituellement, POST, PUT, ou DELETE). Le dictionnaire de stockage request.vars contient l'ensemble (get_vars et post_vars sont fusionnés).

web2py stocke WSGI et les variables d'environnement web2Py dans request.env, par exemple :

request.env.path_info = 'a/c/f'

et les en-têtes HTTP dans les variables d'environnement, par exemple :

request.env.http_host = '127.0.0.1:8000'

Notez que web2py valide toutes les URLs pour éviter les 'directory traversal attacks'.

Les URLs sont seulement autorisées à contenir des caractères alphanumériques, underscores, et des slashes ; les args peuvent contenir des points non consécutifs. Les espaces sont remplacés par des underscores avant validation. Si la syntaxe URL est invalide, web2py retourne un message d'erreur HTTP 400 [http-w] [http-o] .

Si l'URL correspond à une requête pour un fichier statique, web2py lit simplement et retourne (en flux) le fichier requêté.

Si l'URL ne requête pas un fichier statique, web2py procède à la requête dans l'ordre suivant :

  • Parse les cookies.
  • Créé un environnement dans lequel exécuter la fonction.
  • Initialise request, response, ¢ache.
  • Ouvre la session existante our en créé une nouvelle.
  • Exécute les modèles appartenant à l'application requêtée.
  • Exécute la fonction du contrôleur actionnée.
  • Si la fonction retourne un dictionnaire, exécute la vue associée.
  • En cas de succès, valide toutes les transactions ouvertes.
  • Sauve la session.
  • Retourne une réponse HTTP.

Notez que le contrôleur et la vue sont exécutés dans des copies différentes sur le même environnement ; donc, la vue ne voit pas le contrôleur, mais elle voit les modèles et voit les variables retournées par la fonction actionnée par le contrôleur.

Si une exception (autre qu'HTTP) est levée, web2py agit comme suit :

  • Stocke la traceback dans un fichier d'erreur et lui assigne un numéro de ticket.
  • Effectue un rollback sur toutes les transactions à la base de données ouvertes.
  • Retourne une page d'erreur indiquant le numéro de ticket.

Si l'exception est une exception HTTP, c'est alors supposé avoir le comportement attendu (par exemple, une redirection HTTP), et toutes les transactions à la base de données ouvertes sont validées. Le comportement après cela est spécifié par l'exception HTTP elle-même. La classe d'exception HTTP n'est pas une exception standard Python ; elle est définie dans web2py.

Librairies

Les librairies web2py sont exposées aux applications utilisateur comme des objets globaux. Par exemple (request, response, session, cache), classes (helpers, validators, DAL API), et les functions (T et redirect).

Ces objets sont définis dans les fichiers coeur suivants ;

web2py.py
gluon/__init__.py    gluon/highlight.py   gluon/restricted.py  gluon/streamer.py
gluon/admin.py       gluon/html.py        gluon/rewrite.py     gluon/template.py
gluon/cache.py       gluon/http.py        gluon/rocket.py      gluon/storage.py
gluon/cfs.py         gluon/import_all.py  gluon/sanitizer.py   gluon/tools.py
gluon/compileapp.py  gluon/languages.py   gluon/serializers.py gluon/utils.py
gluon/contenttype.py gluon/main.py        gluon/settings.py    gluon/validators.py
gluon/dal.py         gluon/myregex.py     gluon/shell.py       gluon/widget.py
gluon/decoder.py     gluon/newcron.py     gluon/sql.py         gluon/winservice.py
gluon/fileutils.py   gluon/portalocker.py gluon/sqlhtml.py     gluon/xmlrpc.py
gluon/globals.py     gluon/reserved_sql_keywords.py

Notez que beaucoup de ces modules, spécialement dal (Database Abstraction Layer), template (le template de langue), rocket (le serveur web), et html (les helpers) n'ont pas de dépendance et peuvent être utilisés en dehors de web2py.

L'application de base zippée (gzip) fournie avec web2py est

welcome.w2p

Elle est créée lors de l'installation et écrasée lors de mise à jour.

La première fois où vous démarrez web2py, deux nouveaux dossiers sont créés : deposit et applications. Le dossier 'deposit' est utilisé comme stockage temporaire pour installer et désinstaller les applications.

La première fois que vous démarrez web2py et après une mise à jour, l'application "welcome" est zippée dans un fichier "welcome.w2p" pour être utilisée comme application de référence.

Lorsque web2py est mis à jour, il est fourni avec un fichier nommée "NEWINSTALL". Si web2py trouver ce fichier, il comprend qu'une mise à jour a été effectuée, il supprime alors le fichier et créé un nouveau "welcome.w2p".

La version courante de web2py est stockée dans le champ "VERSION" et il suit les standards de notation de versioning sémantiques où l'ID de build est le timestamp du build.

Les tests unitaires de web2py sont dans

gluon/tests/

Il y a plusieurs gestionnaires pour se connecter à différents serveurs web :

cgihandler.py       # discouraged
gaehandler.py       # for Google App Engine
fcgihandler.py      # for FastCGI
wsgihandler.py      # for WSGI
isapiwsgihandler.py # for IIS
modpythonhandler.py # deprecated

("fcgihandler" appelle "gluon/contrib/gateways/fcgi.py" développé par Allan Saddi) et

anyserver.py

qui est un script pour s'interfacer avec de nombreux serveurs web, décrits dans le chapitre 13.

Il y a trois fichiers d'exemples dans le répertoire "examples" :

options_std.py
routes.parametric.example.py
routes.patterns.example.py

Ils sont tous destinées à être copiés à la racine (où web2py.py ou web2py.exe se trouve) et édités selon vos propres préférences. Le premier est un fichier de configuration optionnel qui peut être passé à web2py.py avec l'option -L. Le second est un exemple de mapping d'URL pour un fichier. Il est chargé automatique lorsqu'il est renommé en "routes.py". Le troisième est une syntaxe alternative pour le mapping d'URL, et peut aussi être renommé (ou copié vers) "routes.py".

Les fichiers

app.example.yaml
queue.example.yaml

sont des fichiers d'exemples de configuration utilisés pour le déploiement sur les Google App Engine. Vous pouvez en lire plus à propos d'eux dans le chapitre sur les différentes méthodes de déploiement et sur les pages de documentation Google.

Il y a également des librairies additionnelles, certaines développées par des tiers :

feedparser[feedparser] par Mark Pilgrim pour lire des flux RSS et Atom :

gluon/contrib/__init__.py
gluon/contrib/feedparser.py

markdown2[markdown2] par Trent Mick pour le markup wiki :

gluon/contrib/markdown/__init__.py
gluon/contrib/markdown/markdown2.py

markmin markup:

gluon/contrib/markmin

(voir syntaxe MARKMIN pour plus d'infos)

fpdf créé par Mariano Reingart pour générer des documents PDF :

gluon/contrib/fpdf

Ce n'est pas documenté dans ce livre mais c'est hébergé et documenté ici :

http://code.google.com/p/pyfpdf/

pysimplesoap est un serveur SOAP léger avec une implémentation créée par Mariano Reingart :

gluon/contrib/pysimplesoap/

simplejsonrpc est un client JSON-RPC léger créé également par Mariano Reingart:

jsonrpc

gluon/contrib/simplejsonrpc.py

memcache[memcache] API Python par Evan Martin:

gluon/contrib/memcache/__init__.py
gluon/contrib/memcache/memcache.py

redis_cache

redis
est un module pour stocker le cache dans la base redis :

gluon/contrib/redis_cache.py

gql, un portage de la DAL au Google App Engine :

gluon/contrib/gql.py

memdb, un portage de la DAL par dessus memcache :

gluon/contrib/memdb.py

gae_memcache est une API pour utiliser memcache sur un Google App Engine :

gluon/contrib/gae_memcache.py

pyrtf[pyrtf] pour générer des documents Rich Text Format (RTF), développé par Simon Cusack et revu par Grant Edwards :

gluon/contrib/pyrtf/

PyRSS2Gen[pyrss2gen] développé par Dalke Scientific Software, pour générer les flux RSS :

gluon/contrib/rss2.py

simplejson[simplejson] par Bob Ippolito, la librairie standard pour parser et écrire des objets JSON :

gluon/contrib/simplejson/

Google Wallet [googlewallet] fournit les boutons "payer maintenant" qui lient Google comme processus de paiement :

gluon/contrib/google_wallet.py

Stripe.com [stripe] fournit une API simple pour accepter les cartes de paiement :

gluon/contrib/stripe.py

AuthorizeNet [authorizenet] fournit une API pour accepter les cartes de paiement via le réseau Authorize.net :

gluon/contrib/AuthorizeNet.py

Dowcommerce [dowcommerce] API de procédure pour carte de crédit :

gluon/contrib/DowCommerce.py

PaymentTech API de procédure pour carte de paiement :

gluon/contrib/paymentech.py

PAM[PAM] API d'authentification créée par Chris AtLee:

gluon/contrib/pam.py

Un outil de classification Bayesian pour peupler la base de données avec des données aléatoires à des fins de test :

gluon/contrib/populate.py

Un fichier avec une API pour fonctionner sur Heroku.com :

heroku

gluon/contrib/heroku.py

Un fichier qui autorise l'interaction avec la barre de tâches dans Windows, lorsque web2py est démarré en tant que service :

gluon/contrib/taskbar_widget.py

Des login_methors optionnelles et login_forms pouvant être utilisées pour l'authentification :

gluon/contrib/login_methods/__init__.py
gluon/contrib/login_methods/basic_auth.py
gluon/contrib/login_methods/browserid_account.py
gluon/contrib/login_methods/cas_auth.py
gluon/contrib/login_methods/dropbox_account.py
gluon/contrib/login_methods/email_auth.py
gluon/contrib/login_methods/extended_login_form.py
gluon/contrib/login_methods/gae_google_account.py
gluon/contrib/login_methods/ldap_auth.py
gluon/contrib/login_methods/linkedin_account.py
gluon/contrib/login_methods/loginza.py
gluon/contrib/login_methods/oauth10a_account.py
gluon/contrib/login_methods/oauth20_account.py
gluon/contrib/login_methods/oneall_account.py
gluon/contrib/login_methods/openid_auth.py
gluon/contrib/login_methods/pam_auth.py
gluon/contrib/login_methods/rpx_account.py
gluon/contrib/login_methods/x509_auth.py

web2py contient également un dossier avec des scripts utiles incluant

scripts/setup-web2py-fedora.sh
scripts/setup-web2py-ubuntu.sh
scripts/setup-web2py-nginx-uwsgi-ubuntu.sh
scripts/setup-web2py-heroku.sh
scripts/update-web2py.sh
scripts/make_min_web2py.py
...
scripts/sessions2trash.py
scripts/sync_languages.py
scripts/tickets2db.py
scripts/tickets2email.py
...
scripts/extract_mysql_models.py
scripts/extract_pgsql_models.py
...
scripts/access.wsgi
scripts/cpdb.py

Les setup-web2py-* sont particulièrement utiles car ils déclenchent une installation complète et une configuration de web2py en environnement de production de zéro. Certains d'entre eux sont présentés dans le chapitre 14, mais tous contiennent une documentation à l'intérieur qui explique leur but et leur usage.

Finalement, web2Py inclut ces fichiers requis pour construire les distributions binaires.

Makefile
setup_exe.py
setup_app.py

Ce sont des scripts de mise en place pour py2exe et py2app, respectivement, et ils sont uniquement requis pour construire des distributions binaires de web2py. VOUS NE DEVRIEZ JAMAIS AVOIR BESOIN DE LES DEMARRER.

Les applications web2py contiennent des fichiers additionnels, particulièrement des librairies JavaScript tierces, telles que jQuery, calendar, et Codemirror. Leurs auteurs sont reconnus dans les fichiers directement.

Applications

Les applications développées dans web2py sont composées des parties suivantes :

  • models décrivent une représentation des données comme tables de base de données et les relations entre ces tables.
  • controllers décrivent la logique de l'application et le workflow.
  • views décrivent comment les données devraient être affichées à l'utilisation en utilisant HTML et JavaScript.
  • languages décrivent comment traduire les chaînes de caractères de l'application en de multiples langues supportées.
  • static files ne requièrent par de processing (ex. images, CSS stylesheets, etc).
  • ABOUT et README sont des documents s'expliquant par eux-mêmes.
  • errors stockent les rapports d'erreur générées par l'application.
  • sessions stockent les informations sessions relatives à chaque utilisateur.
  • databases stockent les bases de données SQLite et les informations de tables additionnelles.
  • cache stockent les objets de l'application en cache.
  • modules sont d'autres modules Python optionnels.
  • private les fichiers sont accessibles par les contrôleurs mais ne le sont pas directement par le développeur.
  • uploads les fichiers sont accessibles par les modèles mais pas directement par le développeur (ex. les fichiers uploadés par les utilisateurs de l'application).
  • tests est un répertoire pour stocker les scripts de test, correctifs et maquettes.

Les modèles, vues, contrômeurs, languages, et fichiers statiques sont accessible via l'interface web d'administration [design]. ABOUT, README, et les erreurs sont également accessibles via l'interface d'administration à travers les objets de menu correspondants. Les sessions, cache, modules et fichiers privés sont accessibles par l'application mais ne le sont pas via l'interface d'administration.

Tout est bien organisé dans une structure de répertoire claire répliquée pour toute application web2py installée, afin que l'utilisateur n'ait jamais à accéder directement au systèmes de fichiers :

about
license
cache
controllers
databases
errors
languages
models
modules
private
session
static
tests
uploads
views
__init__.py

__init__.py  ABOUT        LICENSE    models    views
controllers  modules      private    tests     cron
cache        errors       upload     sessions  static

"__init__.py" est un fichier vide qui est requis afin d'autoriser Python (et web2py) à importer les modules dans le répertoire modules.

Notez que l'application admin fournit simplement une interface web aux applications web2py sur le système de fichier du serveur. Les applications web2py peuvent également être créées et développées depuis la ligne de commande ou depuis votre éditeur de texte/IDE préféré ; vous n'avez pas à utiliser obligatoirement l'interface d'administration à travers un navigateur. Une nouvelle application peut être crée manuellement en répliquant la structure de répertoires précédente sous, par exemple, "applications/newapp/" (ou simplement dézipper le fichier welcome.w2p dans le nouveau répertoire de l'application). Les fichiers d'application peuvent être créés et édités depuis la ligne de commande sans avoir à utiliser l'interface web d'administration.

API

Les modèles, contrôleurs, et vues sont exécutées dans un environnement où les objets suivants sont déjà importés pour nous :

Objets globaux:

request
response
session
cache

request, response, session, cache

Internationalisation:

T
internationalization

T

Navigation:

redirect
HTTP

redirect, HTTP

Helpers:

helpers

XML, URL, BEAUTIFY

A, B, BODY, BR, CENTER, CODE, COL, COLGROUP,
DIV, EM, EMBED, FIELDSET, FORM, H1, H2, H3, H4, H5, H6,
HEAD, HR, HTML, I, IFRAME, IMG, INPUT, LABEL, LEGEND,
LI, LINK, OL, UL, META, OBJECT, OPTION, P, PRE,
SCRIPT, OPTGROUP, SELECT, SPAN, STYLE,
TABLE, TAG, TD, TEXTAREA, TH, THEAD, TBODY, TFOOT,
TITLE, TR, TT, URL, XHTML, xmlescape, embed64

CAT, MARKMIN, MENU, ON

Formulaires et tableaux

SQLFORM (SQLFORM.factory, SQLFORM.grid, SQLFORM.smartgrid)

Validateurs:

validators

CLEANUP, CRYPT, IS_ALPHANUMERIC, IS_DATE_IN_RANGE, IS_DATE,
IS_DATETIME_IN_RANGE, IS_DATETIME, IS_DECIMAL_IN_RANGE,
IS_EMAIL, IS_EMPTY_OR, IS_EXPR, IS_FLOAT_IN_RANGE, IS_IMAGE,
IS_IN_DB, IS_IN_SET, IS_INT_IN_RANGE, IS_IPV4, IS_LENGTH,
IS_LIST_OF, IS_LOWER, IS_MATCH, IS_EQUAL_TO, IS_NOT_EMPTY,
IS_NOT_IN_DB, IS_NULL_OR, IS_SLUG, IS_STRONG, IS_TIME,
IS_UPLOAD_FILENAME, IS_UPPER, IS_URL

Bases de données:

DAL

DAL, Field

Pour une rétro-compatibilité, SQLDB=DAL et SQLField=Field. Nous vous encourageons à utiliser la nouvelle syntaxe DAL et Field, plutôt que l'ancienne.

Les autres objets et modules sont définis dans les librairies, mais ils ne sont pas automatiquement imporés depuis qu'ils ne sont pas utilisés aussi souvent.

Les entités du coeur API dans l'environnement d'exécution web2py sontrequest, response, session, cache, URL, HTTP, redirect et T et sont présentés ci-après.

Quelques objets et fonctions, incluant Auth, Crud et Service, sont définis dans "gluon/tools.py" et ils nécessitent d'être importés si nécessaire :

from gluon.tools import Auth, Crud, Service

Ils sont importés dans db.py dans l'application de référence.

Accéder aux API depuis les modules Python

Vos modèles et contrôleurs peuvent importer des modules Python, et ceux-ci peuvent avoir besoin d'utiliser certaines API web2py. Le moyen de le faire est en les important :

from gluon import *

En fait, n'importe quel module Python, même s'il n'est pas importé par une application web2py, peut importer les API web2py aussi longtemps que web2py est dans sys.path.

Il y a tout de même une mise en garde, web2py définit quelques objets globaux (request, response, session, cache, T) qui peuvent uniquement exister lorsqu'une requête HTTP est présente (ou est simulée). Par conséquent, les modules peuvent y accéder sulement s'ils sont appelés depuis une application. Pour ces raisons, ils sont placés dans un conteneur appelé current, qui est un fil d'objet local. Voici un exemple :

Créez un module "/myapp/modules/test.py" qui contient :

from gluon import *
def ip(): return current.request.client

Maintenant, depuis un contrôleur dans "myapp" vous pouvez faire

import test
def index():
    return "Your ip is " + test.ip()

Notez quelques choses :

  • import test cherche d'abord le module dans le dossier courant des modules de l'application, ensuite dans les dossiers listés dans sys.path. Par conséquent, les modules au niveau de l'application prennent toujours le dessus par rapport aux modules Python. Ceci autorise différentes applications à travailler avec différentes versions de leurs modules, sans conflit.
  • Différents utilisateur peuvent appeler la même action index en même temps, ce qui appelle la fonction dans le module et encore une fois, il n'y a pas de conflit car current.request est un objet différent dans les différents threads. Soyez juste vigilant de ne pas accéder à current.request en dehors des fonctions ou classes (i.e., au niveau le plus élevé) dans le module.
  • import test est un raccourci pour from application.appname.modules import test. En utilisant la syntaxe longue, il est possible d'importer les modules depuis d'autres applications.

Pour une uniformité avec le comportement normal de Python, par défaut, web2py ne recharge pas les modules lorsque des changements sont effectués. Maintenant ceci peut être changé. Pour activer la fonctionnalité de recharge automatique pour les modules, utilisez la fonction track_changes comme suit (typiquement dans un fichier de modèle, avant n'importe quel import) :

from gluon.custom_import import track_changes; track_changes(True)

A partir de maintenant, chaque fois qu'un module est importé, l'importateur vérifiera si le fichier source Python (.py) a changé. S'il a changé, le module sera rechargé.

N'appelez pas track_changes dans les modules eux-mêmes.

La fonction "track_changes" ne suit les changements que pour les modules qui sont stockés dans l'application. Les modules qui importent current peuvent accéder à :

  • current.request
  • current.response
  • current.session
  • current.cache
  • current.T

et n'importe quelle autre variable que votre application a choisi de stocker dans le "current". Par exemple un modèle pourrait faire

auth = Auth(db)
from gluon import current
current.auth = auth

et maintenant tous les modules importés peuvent accéder à current.auth.

current et import créent un puissant mécanisme pour construire des modules extensibles et réutilisables pour vos applications.

Attention ! Etant donné from.gluon import current, il est correct d'utiliser current.request et n'importe quel autre object local du thread sauf un ne devrait jamais les assigner à des variables globales dans le modules, tel que dans

request = current.request # WRONG! DANGER!

ni ne devrait utiliser current pour assigner des attributs de classe :

class MyClass:
    request = current.request # WRONG! DANGER!

C'est parce que les objets locaux du thread doivent être extraits au démarrage. Les variables globales, elles, sont définies seulement une fois lorsque le modèle est importé pour la première fois.

Une autre mise en garde doit être faite avec le cache. Vous ne pouvez pas utiliser l'objet cache pour décorer les fonctions dans les modules, car ça ne se comporterait pas correctement. Afin d'effectuer du cache sur une fonction f dans un module vous devez utiliser lazy_cache :

from gluon.cache import lazy_cache

@lazy_cache('key', time_expire=60, cache_model='ram')
def f(a,b,c,): ....

Pensez bien que la clé est définie par l'utilisateur mais doit être uniquement associée à la fonction. Si omis, web2py déterminera automatiquement une clé.

request

request
Storage
request.cookies
user_agent

L'objet request est une instance de la classe web2py omniprésente qui est appelée gluon.storage.Storage, qui étend la classe Python dict. C'est basiquement un dictionnaire, mais les valeurs d'objet peuvent également être accédées comme attributs :

request.vars

revient au même que :

request['vars']

A moins d'un dictionnaire, si un attribut (ou clé) n'existe pas, il ne lève pas d'exception. A la place, il retourne None.

Il est parfois utile de créer vos propres objets de Storage. Vous pouvez également le faire comme suit :

from gluon.storage import Storage
my_storage = Storage() # empty storage object
my_other_storage = Storage(dict(a=1, b=2)) # convert dictionary to Storage

request a les objets/attributs suivants, quelques uns sont également une instance de la classe Storage :

  • request.cookies: un objet Cookie.SimpleCookie() contenant les cookies passés avec la requête HTTP. Il agit comme un dictionnaire de cookies. Chaque cookie est un objet Morsel [morsel].
  • request.env: un objet Storage contenant les variables d'environnement passées au contrôleur, incluant les variables d'en-tête HTTP depuis la requête HTTP et les paramètres standards WSGI. Les variables d'environnement sont toutes converties en minuscules, et les points sont convertis en underscores pour une meilleure mémorisation.
  • request.application: le nom de l'application demandée.
  • request.controller: le nom du contrôleur demandé.
  • request.function: le nom de la fonction demandée.
  • request.extension: l'extension de l'action demandée. Par défaut en "html". Si la fonction du contrôleur renvoie un dictionnaire et ne spécifie pas de vue, c'est utilisé pour déterminer l'extension du fichier de vue qui va afficher le dictionnaire (parsé depuis request.env.path_info).
  • request.folder: le réppertoire de l'application. Par exemple, si l'application est "welcome", request.folder est mis en chemin absolu "/path/to/welcome". Dans vos programmes, vous devriez toujours utiliser cette variable et la fonction os.path.join pour construire les chemins vers les fichiers que vous avez besoin d'accéder. Bien que web2py utilise les chemins absolus, il est de bonne pratique de ne jamais changer explicitement le répertoire courant de travail (quel qu'il soit) tant que ce n'est pas une pratique sécurisé pour le thread.
  • request.now: un objet datetime.datetime stockant la datetime de la requête courante.
  • request.utcnow: un objet datetime.datetime stockant le datetime UTC de la requête courante.
  • request.args: Une liste des composant du chemin URL suivant le nom de la fonction du contrôleur ; équivalent à request.env.path_info.split('/')[3:]
  • request.vars: un objet gluon.storage.Storage contenant tous les paramètres de requête.
  • request.get_vars: un objet gluon.storage.Storage contenant seulement les paramètres passés dans la chaîne de requête (une requête vers /a/c/f?var1=1&var2=2 va mener à {var1: "1", var2: "2"})
  • request.post_vars: un objet gluon.storage.Storage contenant seulement les paramètres passés dans le corps de la requête (habituellement dans les requêtes POST, PUT, ou DELETE).
  • request.client: l'adresse UP du client comme déterminée, si présente, par request.env.http_x_forwarded_for ou par request.env.remote_addr autrement. Bien qu'utile, ceci ne devrait pas être considéré comme valeur sûre car le http_x_forwarded_for peut être sujet à spoofing.
  • request.is_local: True si le client est localhost, False autrement. Devrait fonctionner derrière un proxy si le proxy supporte http_x_forwarded_for.
  • request.is_https: True si la requête utilise le protocol HTTPS, False autrement.
  • request.body: un flux de fichier en lecture seule qui contient le corps de la requête HTTP. Il est automatiquement parsé pour obtenir request.post_vars. Il peut être lu avec request.body.read().
  • request.ajax est True si la fonction est en train d'être appelée via une requête Ajax.
  • request.cid est l' id du composant qui a généré la requête AJax (s'il y a). Vous pouvez obtenir plus d'infos à propos des composants dans le chapitre 12.
  • request.requires_https() évite l'exécution d'autre code si la requête n'est pas sur HTTPS et redirige le visiteur vers la page courante en HTTPS.
  • request.restful c'est un nouveau et très utile décorateur qui peut être utilisé pour changer le comportement par défaut des actions web2py en séparant les requêtes GET/POST/PUSH/DELETE. Il sera présenté avec plus de détails dans le chapitre 10.
  • request.user_agent() parse le champ "user_agent" du client et renvoie l'information sous la forme d'un dictionnaire. C'est utile pour détecter un périphérique mobile. Il utilise "gluon/contrib/user_agent_parser.py" créé par Ross Peoples. Pour voir ce qu'il fait, essayez d'embarquer le code suivant dans une vue :
{{=BEAUTIFY(request.user_agent())}}
  • request.global_settings
    request.global_settings
    contient les paramètres généraux du système web2py. Ils sont définis automatiquement et vous ne devriez pas les changer. Par exemple request.global_settings.gluon_parent contient le chemin complet vers le dossier web2py, request.global_settings.is_pypy détermine si web2py fonctionne sur PyPy.
  • request.wsgi est une accroche qui vous permet d'appeler des applications WSGI tierces depuis les actions internes

Le dernier inclut :

  • request.wsgi.environ
  • request.wsgi.start_response
  • request.wsgi.middleware

Leur usage est présenté à la fin du chapitre.

Comme exemple, l'appel suivant sur un système typique :

http://127.0.0.1:8000/examples/default/status/x/y/z?p=1&q=2

résulte en l'objet request suivant :

request
env

variablevalue
request.applicationexamples
request.controllerdefault
request.functionstatus
request.extensionhtml
request.viewstatus
request.folderapplications/examples/
request.args['x', 'y', 'z']
request.vars<Storage {'p': 1, 'q': 2}>
request.get_vars<Storage {'p': 1, 'q': 2}>
request.post_vars<Storage {}>
request.is_localFalse
request.is_httpsFalse
request.ajaxFalse
request.cidNone
request.wsgi<hook>
request.env.content_length0
request.env.content_type
request.env.http_accepttext/xml,text/html;
request.env.http_accept_encodinggzip, deflate
request.env.http_accept_languageen
request.env.http_cookiesession_id_examples=127.0.0.1.119725
request.env.http_host127.0.0.1:8000
request.env.http_refererhttp://web2py.com/
request.env.http_user_agentMozilla/5.0
request.env.path_info/examples/simple_examples/status
request.env.query_stringremote_addr:127.0.0.1
request.env.request_methodGET
request.env.script_name
request.env.server_name127.0.0.1
request.env.server_port8000
request.env.server_protocolHTTP/1.1
request.env.server_softwareRocket 1.2.6
request.env.web2py_path/Users/mdipierro/web2py
request.env.web2py_versionVersion 2.4.1
request.env.wsgi_errors<open file, mode 'w' at >
request.env.wsgi_input
request.env.wsgi_url_schemehttp

Quelles variables d'environnement sont en fait définies dépend du serveur web. Nous supposons ici l'utilisation du serveur WSGI fourni Rocket. L'ensemble de variables n'est pas très différent lorsque l'on utilise un serveur Web Apache.

Les variables request.env.http_* sont parsées depuis l'en-tête HTTP de la requête.

Les variables request.en.web2py_* ne sont pas parsées depuis l'environnement du serveur web, mais sont créées par web2py dans le cas où vos applications ont besoin de connaître l'emplacement et la version de web2py, et s'il fonctionne sur un environnement Google App Engine (car des optimisations spécifiques peuvent être nécessaires).

Notez également les variables request.env.wsgi_*. Elles sont spécifiques à l'adaptateur wsgi.

response

response
response.body
response.cookies
response.download
response.files
response.flash
response.headers
response.meta
response.menu
response.postprocessing
response.render
response.static_version
response.status
response.stream
response.subtitle
response.title
response.toolbar
response.view
response.delimiters
response.js
response.write
response.include_files
response.include_meta
response.optimize_css
response.optimize_js
response._caller
response.models_to_run

response est une autre instance de la classe Storage. Elle contient :

  • response.body: un objet StringIO dans lequel web2py écrit la sortie du corps de la page. NE JAMAIS CHANGER CETTE VARIABLE.
  • response.cookies: similaire à request.cookies, mais alors que ce dernier contient les cookies envoyés du client au serveur, le premier contient les cookies envoyés par le serveur au client. Le cookie de session est récupéré automatiquement.
  • response.download(request, db): une méthode utilisée pour implémenter la fonction du contrôleur qui autorise le téléchargement des fichiers uploadés. response.download s'attend à ce que le dernier arg dans request.args soit le nom de fichier encodé (i.e., le nom de fichier généré au moment de l'upload et stocké dans le champ d'upload). Il extrait le champ nom de l'upload et le nom de la table aussainsi que le nom de fichier original du nom de fichier encodé. response.download prend deux arguments optionnels : chunk_size définit la taille en octets pour les flux en chunk (défaut à 64K), et attachments détermine si le fichier téléchargé devrait être traité comme une pièce jointe ou non (par défaut à True). Notez, response.download est spécifiquement pour télécharger les fichiers associés avec les champs d'upload de db. Utilisez response.stream (voir après) pour d'autres types de téléchargement de fichiers et de flux. Notez également qu'il n'est pas nécessaire d'utiliser response.download pour accéder aux fichiers uploadés dans le dossier /static -- les fichiers statiques peuvent (et devraient généralement) être accédés directement par l'URL (ex. /app/static/files/myfile.pdf).
  • response.files: une liste de fichiers .css, .js, .coffee, et .less requis par la page. Ils seront automatiquement liés dans la tête du standard "layout.html" via le fichier inclus "web2py_ajax.html". Pour inclure un nouveau fichier CSS, JS, COFFEE ou JS, ajoutez le simplement à la liste. Ceci évitera les duplications. L'ordre est important.
  • response.include_files() génère les tags HTML d'en-tête pour inclure tous les response.files (utilisés dans "views/web2py_ajax.html").
  • response.flash: paramètre optionnel qui peut être inclus dans les vues. Normalement utilisé pour notifier l'utilisateur au sujet de quelque chose qui s'est produit.
  • response.headers: un dict pour les en-têtes de réponse HTTP. Web2py définit quelques en-têtes par défaut, incluant "Content-Length", "Content-Type", et "X-Powered-By" (défini comme web2py). Web2py définit également les en-têtes "Cache-Control", "Expires", et "Pragma" pour éviter le cache côté client, exceptées les requếtes pour les fichiers statiques, pour lesquelles les mécanismes de cache sont activés. Les en-têtes que web2py définir peuvent être surchargées ou supprimées, et de nouveaux en-têtes peuvent être ajoutés (e.g., response.headers['Cache-Control'] = 'private'). Vous pouvez supprimer un en-tête en supprimant sa clé du dictionnaire response.headers (e.g. del response.headers['Custom-Header']), cependant les en-têtes par défaut de web2py seront ré-ajoutés juste avant de renvoyer la réponse. Pour éviter ce comportement, définissez juste la valeur de l'en-tête à None (e.g., pour supprimer l'en-tête Content-Type, response.headers['Content-Type'] = None).
  • response.menu: paramètre optionnel qui peut être inclus dans les vues, normalement utilisé pour passer un menu de navigation en arbre à la vue. Il peut être affiché par l'helper MENU.
  • response.meta: un objet Storage qui contient les informations <meta> telles que response.meta.author, .description, et/ou .keywords. Le contenu de chaque variable meta est automatiquement placé dans le tag META correspondant par le code dans "views/web2py_ajax.html", qui est inclus par défaut dans "views/layout.html".
  • response.include_meta() génère une chaîne qui inclut tous les en-têtes response.meta sérialisés (utilisés dans "views/web2py_ajax.html").
  • response.postprocessing: c'est une liste de fonctions, vide par défaut. Ces fonctions sont utilisées pour filtrer l'objet response à la sortie d'une action, avant d'être envoyé et affiché par la vue. Il peut être utilisé pour implémenter le support d'autres templates de langues.
  • response.render(view, vars): une méthode utilisée pour appeler la vue explicitement à l'intérieur du contrôleur. view est un paramètre optionnel qui est le nom du fichier de vue, vars est un dictionnaire des valeurs nommées passées à la vue.
  • response.session_file: le fichier de flux contenant la session.
  • response.session_file_name: nom du fichier où la session sera sauvée.
  • response.session_id: l'id de la session courante. Il est déterminé automatiquement. NE JAMAIS CHANGER CETTE VARIABLE.
  • response.session_id_name: le nom du cookie de session pour cette application. NE JAMAIS CHANGER CETTE VARIABLE.
  • response.static_version: un numéro de version pour la validation/gestion des fichiers statiques.
  • response.status: le code HTTP (statut) passé à la réponse. Par défaut à 200 (OK).
  • response.stream(file, chunk_size, request=request, attachment=False, filename=None): lorsqu'un contrôleur le retourne, web2py envoie un flux contenant le fichier au client en blocs de taille chunk_size. Le paramètre request est requis pour utiliser le début du chunk dans l'en-tête HTTP. file devrait être un chemin de fichier (pour rétro-compatibilité, il peut aussi être un objet de fichier ouvert, mais ce n'est pas recommandé). Comme noté au-dessus, response.download devrait être utilisé pour retrouver les fichiers stockés via un champ d'upload. response.stream peut être utilisé dans d'autres cas, tels que retourner un fichier temporaire ou un objet StringIO créé par le contrôleur. Si attachment est à True, l'en-tête Content-Disposition sera défini à "attachemnt", et si filename est également fourni, il sera ajouté à l'en-tête Content-Disposition aussi (mais seulement lorsque attachment est à True). S'ils ne sont pas déjà inclus dans response.headers, les en-têtes suivants seront définis automatiquement : Content-Type, Content-Length, Cache-Control, Pragma, et Last-Modified (les trois derniers sont définis pour autoriser le navigateur à faire du cache sur le fichier). Pour surcharger n'importe lequel de ces en-têtes, définissez-les simplement dans response.headers avant d'appeler response.stream.
  • response.subtitle: paramètre optionnel qui peut être inclus dans la vue. Il devrait contenir le sous-titre de la page.
  • response.title: paramètre optionnel qui peut être inclus dans les vues. Il devrait contenir le titre de la page et devrait être affiché dans la page html comme tag title dans l'en-tête.
  • response.toolbar: une fonction qui vous autorise à embarquer une barre d'outils dans la page pour des fins de déboguage {{=response.toolbar()}}. La barre affiche la requête, la réponse, les variables de session et les heures d'accès à la base de données pour chaque requête.
  • response._vars: cette variable est accessible uniquement dans une vie, pas dans l'action. Elle contient les valeurs retournées par l'action à la vue.
  • response._caller: c'est une fonction qui enveloppe tous les appels d'action. Il identifie par défaut la fonction, mais peut être modifié pour capturer des types spéciaux d'exception pour du logging plus précis ; response._caller = lambda f: f()
  • response.optimize_css: peut être défini à "concat,minify,inline" pour concaténer, minimiser et aligner les fichiers CSS inclus par web2py.
  • response.optimize_js: peut être défini à "concat,minify,inline" pour concaténer, minimiser et aligner les fichiers JavaScript inclus par web2py.
  • response.view: le nom du template de la vue qui doit être utilisé pour afficher la page. Il est défini par défaut à :
      "%s/%s.%s" % (request.controller, request.function, request.extension)
      
    
    ou, si le fichier ci-dessus ne peut pas être localisé, à
      "generic.%s" % (request.extension)
      
    
    Changez la valeur de cette variable pour modifier le fichier de vue associé avec une action particulière.
  • response.delimiters par défaut à ('{{','}}'). Il vous permet de changer les délimiteurs de code embarqué dans les vues.
  • response.xmlrpc(request, methods): lorsqu'un contrôleur le retourne, cette fonction expose la méthode via XML-RPC[xmlrpc] . Cette fonction est dépréciée depuis qu'un meilleur mécanisme est disponible et décrit dans le chapitre 10.
  • response.write(text): une méthode pour écrire du texte à l'intérieur du corps de la page sortie.
  • response.js peut contenir du code Javascript. Ce code sera exécuté si et seulement si la réponse est reçue par un composant web2Py comme présenté en chapitre 12.
  • response.models_to_run contient une liste d'expressions régulières qui choississent les modèles à exécuter.
    • Par défaut, ceci est défini automatiquement pour charger les fichiers /a/models/*.py, /a/models/c/*.py, et /a/models/c/f/*.py lorsque /a/c/f est demandé. Vous pouvez définir, par exemple, response.models_to_run = ['myfolder/'] pour forcer seulement l'exécution des modèles à l'intérieur de votre répertoire d'application models/myfolder.
    • NB: response.models_to_run est une liste d'expressions régulières, et non une liste de chemins de fichiers. Les regex sont relatives au répertoire models/, donc n'importe quel fichier de modèle avec un chemin relatif qui correspond à l'une des expressions régulières sera exécutée. Notez également que ceci ne peut pas affecter n'importe quel autre modèle qui a déjà été évalué car ils étaient avant en ordre alphabétique. Ceci étant, si un modèle conditionnel pour un contrôleur orange était orange/orange_model.py et qu'il définit la regex à [.*], ce changement n'affecte pas tout autre modèle précédemment rejeté pour charger un modèle tel que apple/apple_model.py ; il effectue la correspondance avec la nouvelle regex mais a été évalué et rejeté avant que orange/orange_model.py n'ait changé l'expression.
    • Ceci signifie que si vous voulez utiliser models_to_run pour partager des modèles conditionnels entre les contrôleurs, vous devez mettre les modèles dans un sous-répertoire qui sera trié dernier en ordre alphabétique tel que zzz, et ensuite utiliser une expression régulière 'zzz'.

Dès lors que response est un objet gluon.storage.Storage, il peut être utilisé pour stocker d'autres attributs que vous pouvez vouloir passer à la vue. Tant qu'il n'y a pas de restrictions techniques, notre recommandation est de stocker uniquement les variables qui doivent être affichées par toutes les pages dans un layout général ("layout.html").

Quoi qu'il en soit, nous recommandons fortement de vous conforter aux variables listées ici :

response.title
response.subtitle
response.flash
response.menu
response.meta.author
response.meta.description
response.meta.keywords
response.meta.*

car ceci vous rendra plus facile la possibilité de remplacer un fichier standard "layout.html" fourni avec web2py par un autre fichier de layout, un qui utilise le même ensemble de variables.

Les anciennes versions de web2py utilisaient response.author au lieu de response.meta.author et de même pour les autres attributs meta.

session

session
session.connect
session.forget
session.secure
session est une autre instance de la classe Storage. Peu importe ce qui est stocké dans session, par exemple :

session.myvariable = "hello"

il peut être récupéré à n'importe quel moment plus tard :

a = session.myvariable

tant que le code est exécuté dans la même session par le même utilisateur (supposant que l'utilisateur n'a pas supprimé les cookies de session et que la session n'a pas expirée). Etant donné que session est un objet Storage, essayer d'accéder à un attribut/clé qui n'a pas été défini ne lève pas d'exception ; il renvoie None à la place.

L'objet session a trois importantes méthodes. L'une est forget :

session.forget(response)

Elle dit à web2py de ne pas sauver la session. Ceci devrait être utilisé dans les contrôleurs dont les actions sont appelées souvent et n'ont pas besoin de tracer l'activité utilisateur. session.forget() évite au fichier de session d'être écrit, indépendamment du fait qu'il ait été modifié. session.forget(response) déverrouille et ferme le fichier de session. Vous n'aurez que rarement besoin d'appeler cette méthode puisque les sessions ne sont pas enregistrées tant qu'elles ne sont pas changées. Cependant, si la page effectue plusieurs appels Ajax simultanément, c'est une bonne idée pour ces actions appelées via Ajax d'utiliser session.forget(response) (supposant que la session n'est pas nécessaire pour l'action). Autrement, chaque action Ajax devra attendre que la précédente ait terminé (et déverrouillé le fichier de session) avant de démarrer, ce qui ralentit énormément le chargement des pages. Notez bien que les sessions ne sont pas verrouillées si elles sont stockées dans la base.

Une autre méthode est :

session.secure()

qui indique à web2py de définir le cookie de session comme cookie sécurisé. Ceci devrait être fait si l'application fonctionne sur https. En définissant le cookie de session comme sécurisé, le serveur demande au navigateur de ne pas renvoyer le cookie de session au serveur à moins qu'il n'y ait une connexion sécurisée (https) qui soit établie.

L'autre méthode est connect. Par défaut, les sessions sont stockées sur le système de fichiers et un cookie de session est utilisé pour stocker et retrouver le session.id. En utilisant la méthode connect il est possible de dire à web2py de stocker les sessions dans la base de données ou dans les cookies supprimant ainsi le besoin d'accéder au système de fichiers pour la gestion des sessions.

Par exemple pour stocker les sessions dans la base de données :

session.connect(request, response, db, masterapp=None)

db est le nom d'une connexion ouverte à la base de données (telle que retournée par le DAL). Il indique à web2py que vous souhaitez stocker les sessions dans la base de données et non sur le filesystem. session.connect doit venir après db=DAL(...), mais avant n'importe quelle autre action qui requiert la session, par exemple, la définition de Auth.

web2py créé une table :

db.define_table('web2py_session',
                 Field('locked', 'boolean', default=False),
                 Field('client_ip'),
                 Field('created_datetime', 'datetime', default=now),
                 Field('modified_datetime', 'datetime'),
                 Field('unique_key'),
                 Field('session_data', 'text'))

et stocke les sessions cPickled dans le champ session_data.

L'option masterapp=None, par défaut, indique à web2py d'essayer de récupérer une session existante pour l'application avec le nom contenu dans request.application, dans l'application en cours d'exécution.

Si vous voulez partager les sessions entre plusieurs applications, définissez masterapp avec le nom de l'application principale.

Pour "stocker les sessions dans les cookies" plutôt, vous pouvez faire :

session.connect(request,response,cookie_key='yoursecret',compression_level=None)

Ici cookie_key est une clé de chiffrement symétrique. compression_level est un niveau de chiffrement optionnel zlib.

Alors que les sessions en cookie sont souvent recommandées pour l'évolutivité, elles sont cependant limitées en taille. Les larges sessions entraîneront des cookies en erreur.

Vous pouvez vérifier le statut de votre application en affichant à tout moment les variables système request, session et response. Une manière de le faire est de créer une action dédiée :

def status():
    return dict(request=request, session=session, response=response)

Dans la vue "generic.html" ceci est fait en utilisant {{=response.toolbar()}}.

Ne stockez pas de classes définies par l'utilisateur dans une session

Les variables stockées dans la session sont préservées entre les requêtes par la sérialisation.

Les sessions sont récupérées avant que le code du module ne soit exécuté et par conséquent avant que les classes ne soient définies. Par conséquent, les classes définies par l'utilisateur ne peuvent pas être conservées.

Les classes définies dans les modules sont également une zone grise et ne devraient pas être mises dans le stockage. La plupart du temps elles fonctionnent mais peuvent avoir un dysfonctionnement. Ceci peut arriver, par exemple, si vous redémarrez le serveur web et qu'un utilisateur retrouve une session avant que le module soit importé. Le même problème existe lorsque que le serveur web démarre un nouveau processus de travail. Le même problème se présente en environnement distribué.

Sessions séparées

Si vous stockez les sessions sur le filesystem et que vous en avez beaucoup, l'accès au fichier peut devenir un goulot d'étranglement. Une solution est la suivante :

session.connect(request, response, separate=True)

En définissant separate=True web2py stockera les sessions non pas dans le dossier "sessions/" mais dans les sous-dossiers du dossier "sessions/". Le sous-dossier sera créé automatiquement. Les sessions avec le même préfixe seront dans le même sous-dossier. Encore, notez bien que ceci doit être appelé avant n'importe quelle logique qui peut requérir la session.

cache

cache
cache.ram
cache.disk
cache est un objet global également disponible dans l'environnement d'exécution web2py. Il a deux attributs :

  • cache.ram: le cache de l'application dans la mémoire principale.
  • cache.disk: le cache de l'application sur le disque.

cache peut être appelé, ceci lui permet d'être utilisé comme décorateur pour mettre en cache des actions et des vues.

Les exemples suivants permettent de mettre en cache la fonction time.ctime() en RAM :

def cache_in_ram():
    import time
    t = cache.ram('time', lambda: time.ctime(), time_expire=5)
    return dict(time=t, link=A('click me', _href=request.url))

La sortie de lambda: time.ctime() est mis en cache pour 5 secondes. La chaîne de caractères 'time' est utilisée comme clé de cache.

L'exemple suivant met en cache la fonction time.ctime() sur le disque :

def cache_on_disk():
    import time
    t = cache.disk('time', lambda: time.ctime(), time_expire=5)
    return dict(time=t, link=A('click me', _href=request.url))

La sortie de lambda: time.ctime() est mise en cache sur le disque (en utilisant le module shelve) pour 5 secondes.

Notez le second argument de cache.ram et cache.disk doit être une fonction ou un objet qui peut être appelé. Si vous souhaitez mettre en cache un objet existant plutôt que la sortie d'une fonction, vous pouvez simplement le retourner via une fonction lambda :

cache.ram('myobject', lambda: myobject, time_expire=60*60*24)

L'exemple suivant met en cache la fonction time.ctime() en RAM et sur le disque :

def cache_in_ram_and_disk():
    import time
    t = cache.ram('time', lambda: cache.disk('time',
                       lambda: time.ctime(), time_expire=5),
                       time_expire=5)
    return dict(time=t, link=A('click me', _href=request.url))

La sortie de lambda: time.ctime() est mise en cache sur le disque (en utilisant le module shelve) et ensuite en RAM pour 5 secondes. web2py regarde d'abord en RAM puis ensuite sur le disque s'il ne trouve rien. Si ce n'est ni en RAM ni sur le disque, lambda: time.ctime() est exécuté et le cache est mis à jour. Cette technique est utile dans un environnement multi-processeur. Les deux fois n'ont pas à être les mêmes.

L'exemple suivant met en cache (en RAM) la sortie de la fonction contrôleur (mais pas la vue) :

cache controller
@cache(request.env.path_info, time_expire=5, cache_model=cache.ram)
def cache_controller_in_ram():
    import time
    t = time.ctime()
    return dict(time=t, link=A('click me', _href=request.url))

Le dictionnaire retourné par cache_controller_in_ram est mis en cache en RAM pour 5 secondes. Notez que le résultat d'un select sur une base de données ne peut pas être mis en cache sans avoir été sérialisé en premier lieu. Un meilleur moyen est de mettre en cache le select de la base de données directement en utilisant l'argument cache de la méthode select.

L'exemple suivant met en cache la sortie de la fonction contrôleur sur le disque (mais pas la vue) :

@cache(request.env.path_info, time_expire=5, cache_model=cache.disk)
def cache_controller_on_disk():
    import time
    t = time.ctime()
    return dict(time=t, link=A('click to reload',
                              _href=request.url))

Le dictionnaire retourné par cache_controller_on_disk est mis en cache sur le disque pendant 5 secondes. Souvenez-vous que web2py ne peut pas mettre en cache un dictionnaire qui contient des objets non sérialisables.

Il est également possible de mettre en cache la vue. Le truc est de définir la vue dans la fonction contrôleur, afin que le contrôleur retourne une chaîne. Ceci est fait en retournant response.render(d)d est le dictionnaire que nous souhaitons passer à la vue. L'exemple suivant met en cache la sortie de la fonction contrôleur en RAM (incluant la vue rendue) :

cache view
@cache(request.env.path_info, time_expire=5, cache_model=cache.ram)
def cache_controller_and_view():
    import time
    t = time.ctime()
    d = dict(time=t, link=A('click to reload', _href=request.url))
    return response.render(d)

response.render(d) returns the rendered view as a string, which is now cached for 5 seconds. This is the best and fastest way of caching.

Nous recommandons l'utilisation de @cache.action à partir de web2py &gt; 2.4.6

Notez que time_expire est utilisé pour comparer la date actuelle avec la date à laquelle a été sauvé l'objet requêté en cache. Ceci n'affecte pas les futures requêtes. Ceci permet à time_expire d'être défini dynamiquement lorsqu'un objet est requêté plutôt que d'être corrigé lorsque l'objet est sauvé. Par exemple :

message = cache.ram('message', lambda: 'Hello', time_expire=5)

Maintenant, supposez que l'appel suivant est fait 10 secondes après l'appel ci-dessus :

message = cache.ram('message', lambda: 'Goodbye', time_expire=20)

Etant donné que time_expire est défini à 20 secondes dans le second appel et que seulement 10 secondes sont passées depuis que le premier message a été sauvé, la value "Hello" sera renvoyée depuis le cache, et ne sera pas mis à jour avec "Goodbye". La valeur time_expire de 5 secondes dans le premier appel n'a aucun impact sur le second appel.

Définir time_expire=0 (ou une valeur négative) force l'objet en cache à être actualisé (car le temps écoulé depuis la première sauvegarde sera toujours > °), et définir time_expire=None force à retrouver la valeur en cache, sans tenir compte du temps écoulé depuis qu'il a été sauvegardé (si time_expire est toujours None, l'objet en cache n'expirera effectivement jamais).

Vous pouvez nettoyer une ou plusieurs variables en cache avec

cache clear
cache.ram.clear(regex='...')

regex est une expression régulière correspondant à toutes les clés que vous souhaitez supprimer du cache. Vous pouvez aussi nettoyer un seul objet avec :

cache.ram(key, None)

key est la clé de l'objet en cache.

Il est également possible de définir d'autres mécanismes de cache tel que memcache. Memcache est disponible via gluon.contrib.memcache et est présenté plus en détail dans le chapitre 14.

Faites attention lors d'une mise en cache à vous souvenir que mettre en cache est généralement au niveau applicatif et non au niveau utilisateur. Si vous avez besoin, par exemple, de mettre en cache du contenu spécifique à un utilisateur, choisissez une clé qui inclut l'id de l'utilisateur.

L'application admin pour une application vous laisse voir les clés de cache (et nettoyer le cache). Vous pouvez y accéder depuis la fenêtre de gestion admin de la base de données.

cache.action

Web2py suppose par défaut que le contenu renvoyé ne va pas être mis en cache, car cela réduit les défauts d'une mauvaise mise en cache de la page côté client.

Par exemple, lorsque vous envoyez un formulaire à l'utilisateur, ou une liste d'enregistrements, la page web ne devrait pas avoir été mise en cache, car d'autres utilisateurs peuvent avoir inséré de nouveaux enregistrements sur la table que vous envoyez.

A la place, si vous montrez à l'utilisation une page wiki dont le contenu ne changera jamais (ou change une fois par semaine), il est utile de stocker cette page, mais il est également plus utile d'indiquer au client que la page n'est pas sur le point de changer.

Ceci est possible en envoyant certains en-têtes spécifiques avec la page : lorsque le navigateur client reçoit le contenu, il est stocké dans le cache du navigateur et ne sera pas redemandé à nouveau au site. Bien sûr, c'est une amélioration majeure pour un site public.

Web2py > 2.4.6 a introduit un nouveau décorateur cache.action pour autoriser un moyen plus intelligent de gérer cette situation. cache.action peut être utilisé :

  • pour définir des en-têtes de cache intélligents
  • pour mettre en cache les résultats en conséquence

NB: il fera l'un ou l'autre ou les deux.

Le principal problème avec la mise en cache d'une vue avec @cache(request.env.path_info, time_expire=300, cache_model=cache.ram) est que request.env.path_info en tant que clé peut mener à de nombreux problèmes tels que

  1. Les variables URL ne sont pas considérées
    • vous avez mis en cache le résultat de /app/default/index?search=foo : pour les 300 prochaines secondes /app/default/index?search=bar va retourner exactement la même chose que /app/default/index?search=foo
  2. L'utilisateur n'est pas considéré
    • votre utilisateur accède souvent à une page et vous choisissez de la mettre en cache. Cependant, vous avez mis en cache le résultat de /app/default/index en utilisant request.env.path_info comme clé, donc un autre utilisateur verra une page qui n'est pas pour lui
    • vous avez mis en cache une page pour "Bill", mais "Bill" a accédé la page depuis le bureau. Maintenant il essaie d'y accéder depuis son téléphone : si vous avez préparé un template pour les mobiles qui est différent du standard, "Joe" ne le verra pas.
  3. La langue n'est pas considérée
    • lorsque vous mettez en cache une page, si vous utilisez T() pour certains éléments, la page sera stockée avec une traduction corrigée
  4. La méthode n'est pas considérée
    • Lorsque vous mettez en cache une page, vous devriez seulement mettre en cache si c'est le résultat d'une requête GET
  5. Le code de statut n'est pas considéré
    • Lorsque vous avez mis en cache la page pour la première fois, quelque chose s'est mal passée et vous avez retourné une jolie page 404. VOus ne voulez pas mettre en cache les erreurs ^_^

Au lieu de laisser les utilisateurs écrire beaucoup de code correctif pour prendre en charge tous ces problèmes, cache.action a été créé. Il va utiliser par défaut des en-têtes de cache intelligents pour laisser le navigateur mettre en cache le résultat : si vous lui passez un modèle de cache, il va également définir la meilleure clé automatiquement, afin que différentes versions de la même page puissent être stockées et récupérées en conséquence (par exemple, une pour les utilisateurs anglais, et une autre pour les utilisateurs espagnols).

Il prend de nombreux paramètres, avec des valeurs par défaut pré-déterminées:

  • time_expire : l'habituelle valeur par défaut de 300 secondes
  • cache_model : par défaut à None. Cela signifie que @cache.action ne modifiera uniquement les en-têtes par défaut pour laisser le navigateur client mettre le contenu en cache
    • si vous mettez par exemple, cache.ram, le résultat sera stocké dans le cache également
  • prefix : si vous voulez préfixer la clé auto-générée (utile pour la nettoyer plus tard, avec par exemple cache.ram.clear(prefix*))
  • session : si vous voulez prendre en compte la session, par défaut à False
  • vars : si vous voulez prendre en compte les variables d'URL, par défaut à True
  • lang : si vous voulez prendre en compte la langue, par défaut à True
  • user_agent : si vous voulez prendre en compte le user agent, par défaut à False
  • public : si vous voulez la même page pour tous les utilisateurs qui y accèderont, par défaut à True
  • valid_statuses : par défaut à None. cache.client va mettre en cache uniquement les pages requêtées avec la méthode GET, dont les codes de statut commencent par 1, 2 ou 3. Vous pouvez transmettre une liste de codes de statuts (lorsque vous voulez que des pages soient mises en cache avec ces statuts, par exemple status_codes=[200] va mettre en cache uniquement les pages qui vont résulter en une page avec un code de statut 200)
  • quick : par défaut à None, mais vous pouvez transmettre une liste d'initiales pour définir une fonctionnalité en particulier :
    • Session, Vars, Lang, User_agent, Public e.g. @cache.action(time_expire=300, cache_model=cache.ram, quick='SVP') est identique à @cache.action(time_expire=300, cache_model=cache.ram, session=True, vars=True, public=True)

"Consider" signifie par exemple pour vars, que vous souhaitez mettre en cache différentes pages si vars sont différents, alors /app/default/index?search=foo ne sera pas le même pour /app/default/index?search=bar Quelques paramètres en surchargent d'autres, donc, par exemple, si vous définissez session=True, public=True le dernier sera ignoré. A utiliser à bon escient !

URL

URL

La fonction URL is l'une des fonctions les plus importantes dans web2py. Elle génère des URL internes pour les actions et les fichiers statiques.

Voici un exemple :

URL('f')

est traduit en

/[application]/[controller]/f

Notez que la sortie de la fonction URLdépend du nom de l'application courante, du contrôleur appelant, et d'autres paramètres. web2py supporte le mapping d'URL de d'URL inverse. Le mapping URL vous permet de redéfinir le format des URLs externes. Si vous utilisez la fonction URL pour générer toutes les URLs internes, alors les ajouts et modifications aux mapping URL éviteront les liens brisés à l'intérieur d'une application web2py.

Vous pouvez transmettre des paramètres additionels à la fonction URL, i.e., des arguments dans le chemin URL (args) et des variables (vars) :

URL('f', args=['x', 'y'], vars=dict(z='t'))

est interprété en

/[application]/[controller]/f/x/y?z=t

Les attributs args sont automatiquement parsés, décodés et finalement stockés dans request.args par web2py. De même, les vars sont parsées, décodées et ensuite stockées dans request.vars. args et vars fournissent le mécanisme de base par lequel web2py échange les informations avec le navigateur client.

Si args contient seulement un élément, il n'est pas nécessaire de le transmettre dans une liste.

Vous pouvez également utiliser la fonction URL pour générer les URLs des actions dans les autres contrôleurs et dans les autres applications :

URL('a', 'c', 'f', args=['x', 'y'], vars=dict(z='t'))

est interprété en

/a/c/f/x/y?z=t

Il est également possible de spécifier l'application, le contrôleur et la fonction en utilisant les arguments nommés :

URL(a='a', c='c', f='f')

Si le nom de l'application a est manquant, l'application courante est utilisée.

URL('c', 'f')

Si le nom du contrôleur c est manquant, le contrôleur courant est utilisé.

URL('f')

Au lieu de transmettre le nom d'une fonction du contrôleur il est également possible de transmettre la fonction elle-même

URL(f)

Pour les raisons mentionnées précédemment, vous devriez toujours utiliser la fonction URL pour générer les URLs des fichiers statiques pour vos applications. Les fichiers statiques sont stockés dans le sous-dossier static de l'application (c'est ici qu'ils sont stockés lorsqu'ils sont envoyés par l'interface d'administration). web2py fournir un contrôleur virtuel 'static' dont le travail est de récupérer les fichiers depuis le sous-dossier static, déterminer le type de contenu, et transmettre sous forme de flux le fichier au client. L'exemple suivant génère l'URL pour le fichier statique "image.png" :

URL('static', 'image.png')

est interprété en

/[application]/static/image.png

Si le fichier statique est dans un sous-dossier du dossier static, vous pouvez inclure le(s) sous-dossier(s) dans le nom du fichier. Par exemple, pour générer : If the static file is in a subfolder within the static folder, you can include the subfolder(s) as part of the filename. For example, to generate:

/[application]/static/images/icons/arrow.png

vous pourriez utiliser :

URL('static', 'images/icons/arrow.png')

Vous n'avez pas besoin d'encoder/échapper les arguments args et vars ; ceci est fait automatiquement pour vous.

Par défaut, l'extension correspondant à la requête courante (qui peut être trouvée dans request.extension) est ajoutée à la fonction, à moins que request.extension soit html, la valeur par défaut. Ceci peut être surchargé en incluant explicitement un extension dans le nom de la fonction URL(f='name.ext') ou avec l'argument extension :

URL(..., extension='css')

L'extension courante peut être explicitement supprimée :

URL(..., extension=False)

URLs absolues

Par défaut, URL génère des URLs relatives. Cependant, vous pouvez également générer des URLs absolues en spécifiant les arguments scheme et host (ce qui est utile, par exemple, lorsque vous insérez des URLs dans un email) :

URL(..., scheme='http', host='www.mysite.com')

Vous pouvez automatiquement inclure le schéma et l'hôte de la requête courante en définissant simplement les arguments à True.

URL(..., scheme=True, host=True)

La fonction URL accepte également un argument port pour spécifier le port du serveur si nécessaire.

URLs signées numériquement

digitally signed URL

Lorsque vous générez une URL, vous avez la possibilité de la signer numériquement. Ceci ajoutera une variable GET _signature qui peut être vérifiées par le serveur. Ceci peut être fait de deux manières.

Vous pouvez transmettre à la fonction URL les arguments suivants :

  • hmac_key: la clé pour signer l'URL (une chaine de caractères)
  • salt: une chaine optionnelle pour saler les données avant des les signer
  • hash_vars: une liste optionnelle de noms de variables depuis la chaine de requête de l'URL (i.e. les variables GET) pour être inclus dans la signature. Il peut également être défini à True (par défaut) pour inclure toutes les variables, ou False pour n'inclure aucune variable.

Voici un exemple d'utilisation :

KEY = 'mykey'

def one():
    return dict(link=URL('two', vars=dict(a=123), hmac_key=KEY))

def two():
    if not URL.verify(request, hmac_key=KEY): raise HTTP(403)
    # do something
    return locals()

Ceci rend l'action two accessible uniquement via une URL signée. Une URL signée ressemble à :

'/welcome/default/two?a=123&_signature=4981bc70e13866bb60e52a09073560ae822224e9'

Note, la signature numérique est vérifiée via la fonction URL.verify. URL.verify prend également les arguments hmac_key, salt, et hash_vars décrits au-dessus, et leurs valeurs doivent correspondre aux valeurs transmises par la fonction URL lorsque la signature a été créée afin de valider l'URL.

Une autre possibilité d'utilisation plus sophistiquée mais plus commune des URL signées numériquement est une conjonction avec Auth. Un exemple illustre bien mieux le cas :

@auth.requires_login()
def one():
    return dict(link=URL('two', vars=dict(a=123), user_signature=True)

@auth.requires_signature()
def two():
    # do something
    return locals()

Dans ce cas hmac_key est automatiquement généré et partagé au sein de la session. Ceci permet à l'action two de déléguer tout contrôle d'accès à l'action one. Si le lien est généré et signé, il est valide ; sinon il ne l'est pas. Si le lien est volé par un autre utilisateur, le lien sera invalide.

Il est de bonne pratique de toujours signer numériquement les callbacks Ajax. Si vous utilisez la fonction web2py LOAD, il y a un argument user_signature qui peut être utilisé dans cet optique :

{{=LOAD('default', 'two', vars=dict(a=123), ajax=True, user_signature=True)}}

HTTP et redirect

HTTP
redirect

web2py définit uniquement une nouvelle exception appelée HTTP. Cette exception peut être levée n'importe où dans un modèle, un contrôleur ou une vue avec la commande :

raise HTTP(400, "my message")

Il mène le flux de contrôle à bypasser le code utilisateur, revenir à web2py et retourner une réponse HTTP telle que :

HTTP/1.1 400 BAD REQUEST
Date: Sat, 05 Jul 2008 19:36:22 GMT
Server: Rocket WSGI Server
Content-Type: text/html
Via: 1.1 127.0.0.1:8000
Connection: close
Transfer-Encoding: chunked

my message

Le premier argument de HTTP est le code HTTP de statut. Le second est la chaîne de caractères qui sera retournée comme corps de la réponse. Des arguments supplémentaires optionnels sont utilisés pour contruire l'en-tête de la réponse HTTP. Par exemple :

raise HTTP(400, 'my message', test='hello')

génère :

HTTP/1.1 400 BAD REQUEST
Date: Sat, 05 Jul 2008 19:36:22 GMT
Server: Rocket WSGI Server
Content-Type: text/html
Via: 1.1 127.0.0.1:8000
Connection: close
Transfer-Encoding: chunked
test: hello

my message

Si vous ne voulez pas effectuer de commit sur la transaction en cours avec la base de données, effectuez un rollback avant de lever l'exception.

Toute exception autre que HTTP force web2py à effectuer un rollback sur toute transaction en cours avec la base, loguer le traceback de l'erreur, préparer un ticket pour l'utilisateur, et retourner une page standard d'erreur.

Cela signifie que seulement HTTP peut être utilisée pour du contrôle de flux à travers les pages. Toutes les autres exceptions doivent être capturées par l'application, autrement web2py émettra automatiquement un ticket.

La commande :

redirect('http://www.web2py.com')

est simplement un raccourci pour :

raise HTTP(303,
           'You are being redirected <a href="%s">here</a>' % location,
           Location='http://www.web2py.com')

Les arguments nommés de la méthode d'initialisation HTTP sont traduits en directive d'en-têtes HTTP, dans ce cas, la cible de la redirection. redirect prend un second argument optionnel, qui est le code de statut HTTP pour la redirection (303 par défaut). Changez ce code à 307 pour une redirection temporaire ou à 301 pour une redirection permanente.

Le moyen le plus commun d'utiliser la redirection est de rediriger vers d'autres pages au sein de la même application et (optionnellement) de transmettre des paramètres :

redirect(URL('index', args=(1,2,3), vars=dict(a='b')))

Dans le chapitre 12, nous discuterons des composants web2py. Ils transforment les requêtes Ajax en action web2py. Si l'action appelée exécute une redirection, il se peut que vous souhaitiez que la requête Ajax suive la redirection ou que la page entière exécute la redirection de la requête Ajax. Dans ce dernier cas vous pouvez définir :

redirect(...,client_side=True)

Internationalisation, et pluralisation avec T

T
internationalization

L'objet T est le traducteur de langue. Il constitue une simple instance globale de la classe web2py gluon.language.translator. Toutes les constantes de chaine de caractères (et seulement les constantes) devraient être marquées par T, par exemple :

a = T("hello world")

Les chaines qui sont marquées avec T sont identifiées par web2py comme ayant besoin de traduction et seront traduites lorsque le code (dans le modèle, le contrôleur ou la vue) est exécuté. Si la chaîne à traduire n'est pas une constante mais une variable, elle sera ajoutée au fichier de traduction au démarrage (sauf sur GAE) pour être traduit plus tard.

L'objet T peut également contenir des variables interpolées et supporter de multiples syntaxes équivalentes :

a = T("hello %s", ('Tim',))
a = T("hello %(name)s", dict(name='Tim'))
a = T("hello %s") % ('Tim',)
a = T("hello %(name)s") % dict(name='Tim')

La dernière syntaxe est recommandée car elle permet de rendre la traduction plus simple. La première chaîne est traduite selon le fichier de langue demandé et la variable name est remplacée indépendamment du langage.

Vous poupvez concaténer des chaînes traduites avec des chaînes normales :

T("blah ") + name + T(" blah")

Le code suivant est également autorisé et souvent préférable :

T("blah %(name)s blah", dict(name='Tim'))

ou la syntaxe alternative

T("blah %(name)s blah") % dict(name='Tim')

Dans les deux cas, la traduction survient avant que la variable name ne soit substituée dans l'expression "%(name)s". L'alternative suivante ne devrait PAS ÊTRE UTILISEE :

T("blah %(name)s blah" % dict(name='Tim'))

car la traduction aurait lieu après la substitution.

Déterminer la langue

Le langage demandé est déterminé par le champ "Accept-Language" dans les en-têtes HTTP, mais cette sélection peut être surchargée avec du code en requêtant un fichier spécifique, par exemple :

T.force('it-it')

qui va lire le fichier de langue "languages/it-it.py". Les fichiers de langue peuvent être créés et édités via l'interface d'administration.

Vous pouvez également forcer une langue pour une chaîne de caractères :

T("Hello World", language="it-it")

Dans le cas où plusieurs langues sont demandées, par exemple "it-it, fr-ft", web2py essaie de localiser les fichiers de traduction "it-it.py" et "fr-fr.py". Si aucun des fichiers demandés n'est présent, il essaie de revenir sur "it.py" et "fr.py". Si ces fichiers ne sont pas présents, il revient par défaut à "default.py". S'il n'est pas présent non plus, il n'y aura pas de traduction. La règle la plus générale est que web2py essaie "xx-xy-yy.py", "xx-xy.py", "xx.py", "default.py" pour chaque langue acceptée "xx-xy-yy" essayant de trouver la meilleure correspondance selon la préférence du visiteur.

Vous pouvez désactiver les traductions complètement via

T.force(None)

Normalement, la traduction des chaînes est évaluée discrètement lorsque la vue est rendue ; d'où la méthode du traducteur force ne devrait pas être appelée dans une vue.

Il est possible de désactiver cette évaluation discrète via

T.lazy = False

Par ce moyen, les chaînes sont traduites immédiatement par l'opérateur T basé sur la langue actuellement acceptée ou la langue forcée.

Il est égaleemnt possible de désactiver l'évaluation discrète pour chaque chaîne individuellement :

T("Hello World", lazy=False)

Un problème standard est le suivant. L'application originale est en anglais. Supposons qu'il y a un fichier de traduction (par exemple Italien, "it-it.py") et que le client HTTP déclare qu'il accepte aussi bien l'Anglais (en) que l'Italien (it-it) dans cet ordre. La situation suivante (non voulue) survient : web2py ne sait pas que la valeur par défaut est l'Anglais (en). De ce fait, il préfère tout traduire en Italien (it-it) car il a seulement trouvé le fichier de traduction en Italien. S'il n'avait pas trouvé le fichier "it-it.py", il aurait utilisé la langue par défaut (Anglais).

Il y a deux solutions pour ce problème : créer un fichier de traduction pour l'Anglais qui serait redondant et inutile, ou mieux, dire à web2py quelle langue devrait être utilisée comme langue par défaut (pour les chaînes codées dans l'application). Ceci peut être fait avec :

T.set_current_languages('en', 'en-en')

Il stocke dans T.current_languages une liste des langues qui ne nécessitent pas de traduction et force un rechargement des fichiers de langage.

Notez que "it" et "it-it" sont des langues différentes du opint de vue de web2py. Pour supporter les deux, l'un aurait besoin de deux fichiers de traduction, toujours en minuscules. Le cas est vrai pour tous les autres langages.

Le langage actuellement supporté est stocké dans

T.accepted_language

Traduire les variables

T(...) ne traduit pas uniquement les chaines de caractères mais peut également traduire les valeurs stockées dans les variables :

>>> a="test"
>>> print T(a)

Dans ce cas, le mot "test" est traduit, mais s'il n'est pas trouvé et si le système de fichier est autorisé en écriture, il sera ajouté à la liste des mots à traduire dans le fichier de langue.

Notez que ceci peut résulter en beaucoup de lecture/écriture de fichier sur le disque, et donc vous pourriez vouloir le désactiver :

T.is_writable = False

empêche T de mettre à jour dynamiquement les fichiers de langage.

Commentaires et traductions multiples

Il est possible que la même chaine apparaisse sous différents contextes dans l'application et ait besoin besoin de différentes traductions selon le contexte. Afin de faire ceci, il est possible d'ajouter des commentaires à la chaine originale. Les commentaires ne seront pas affichés mais seront utilisés par web2py pour déterminer la traduction la plus appropriée. Par exemple :

T("hello world ## first occurrence")
T("hello world ## second occurrence")

Le texte suivant le ##, incluant les double ##, sont des commentaires.

Moteur de pluralisation

Depuis la version 2.0, web2py inclut un système très puissant de pluralisation (PS). Ceci signifie que lorsqu'un texte marqué pour traduction dépend d'une variable numérique, il peut être traduit différemment selon la valeur numérique. Par exemple, en anglais on pourrait afficher :

x book(s)

avec

a book (x==1)
5 books (x==5)

L'Anglais a une forme singulière et une forme plurielle. La forme plurielle est construite en ajoutant un "-s" ou "-es" ou en utilisant une forme exceptionnelle. web2py fournit un moyen de définir les règles de pluralisation pour chaque langue, ainsi que les exceptions aux règles par défaut. En fait, web2py connait déjà les règles de pluralisation de nombreux langages. Il connait, par exemple, que le Slovénien a une seule forme singulière et 3 formes plurielles (pour x==1, x==3 ou x==4 et x>4). Ces règles sont encodées dans les fichiers "gluon/contrib/plural_rules/*.py" et de nouveaux fichiers peuvent être créés. Les pluralisations explicites pour des mots sont créées en éditant les fichiers de pluralisation en utilisant l'interface d'administration.

Par défaut, le PS (pluralization system) n'est pas activé. Il est déclenché par l'argument symbol de la fonction T. Par exemple :

T("You have %s %%{book}", symbols=10)

Maintenant le PS est activé pour le mot "book" and pour le nombre 10. Le résultat en Anglais sera : "You have 10 books". Notez bien que "book" a été pluralisé en "books".

Le PS consiste en 3 parties :

  • les espaces réservés %%{} pour marquer les mots dans l'invite T
  • une règle pour donner une décision sur la forme de mot à utiliser ("rules/plural_rules/*.py")
  • un dictionaire avec des formes de mot au pluriel ("app/languages/plural-*.py")

La valeur des symboles peut être une simple variable, une liste/tuple de variables, ou un dictionnaire.

L'espace réservé %%{} consiste en 3 parties :

%%{[<modifier>]<word>[<parameter>]},

où :

<modifier>::= ! | !! | !!!
<word> ::= any word or phrase in singular in lower case (!)
<parameter> ::= [index] | (key) | (number)

Par exemple :

  • %%{word} est équivalent à %%{word[0]} (si aucun modificateur n'est utilisé).
  • %%{word[index]} est utilisé lorsque les symboles sont un tuple. symbols[index] nous donne un nombre utilisé pour prendre la décision sur la forme de mot à choisir.
  • %%{word(key)} est utilisé pour obtenir le paramètre numérique de symbols[key]
  • %%{word(number)} permet de définir un number directement (e.g.: %%{word(%i)})
  • %%{?word?number} retourne "word" si number==1, retourne le number sinon
  • %%{?number} ou %%{??number} retourne number si number!=1, ne retourne rien sinon
T("blabla %s %%{word}", symbols=var)

%%{word} by default means %%{word[0]}, où [0] est un index d'objet sous forme de tuple de symboles.

T("blabla %s %s %%{word[1]}", (var1, var2))

PS est utilisé pour "word" et "var2" respectivement.

Vous pouvez utiliser différents espaces %%{} avec un seul index :

T("%%{this} %%{is} %s %%{book}", var)

ou

T("%%{this[0]} %%{is[0]} %s %%{book[0]}", var)

Ils génèrent :

var  output
------------------
 1   this is 1 book
 2   these are 2 books
 3   these are 2 books

De la même manière, vous pouvez transmettre un dictionnaire aux symboles :

T("blabla %(var1)s %(wordcnt)s %%{word(wordcnt)}",
  dict(var1="tututu", wordcnt=20))

qui produit

blabla tututu 20 words

Vous pouvez remplacer "1" avec n'importe quel mot que vous souhaitez par cet emplacement %%{?word?number}. Par exemple

T("%%{this} %%{is} %%{?a?%s} %%{book}", var)

produit :

var  output
------------------
 1   this is a book
 2   these are 2 books
 3   these are 3 books
 ...

Inside %%{...} you can also use the following modifiers:

  • ! pour mettre en lettres capitales le texte (équivalent à string.capitalize)
  • !! pour mettre en lettres capitales chaque mot (équivalent à string.title)
  • !!! pour mettre en lettres capitales tous les caractères (équivalent à string.upper)

Notez que vous pouvez utiliser \ comme caractère d'échappement pour ! and ?.

Traductions, pluralisation, et MARKMIN

Vous pouvez également utiliser la puissante syntaxe MARKMIN dans les chaînes de traduction en remplaçant

T("hello world")

par

T.M("hello world")

Maintenant, la chaîne accepte le marquage MARKMIN tel que décrit dans Chapter 5

Cookies

cookies

web2py utilise les modules de cookies Python pour gérer les cookies.

Les cookies en provenance du navigateur sont stockés dans request.cookies et les cookies envoyés par le serveur sont dans response.cookies.

Vous pouvez définir un cookie come suit :

response.cookies['mycookie'] = 'somevalue'
response.cookies['mycookie']['expires'] = 24 * 3600
response.cookies['mycookie']['path'] = '/'

La seconde ligne indique au navigateur de conserver le cookie pour 24 heure. La troisième ligne indique au navigateur de renvoyer le cookie à n'importe quelle application (chemin URL) sur le domaine courant. Notez que si vous ne spécifiez pas de chemin pour le cookie, le navigateur utilisera le chemin de l'URL qui a été demandée, ainsi le cookie sera uniquement renvoyé au serveur lorsque la même URL est demandée.

Le cookie peut être sécurisé avec :

response.cookies['mycookie']['secure'] = True

Ceci indique au navigateur de seulement renvoyer le cookie sur HTTPS et jamais sur HTTP.

Le cookie peut être retrouvé avec :

if request.cookies.has_key('mycookie'):
    value = request.cookies['mycookie'].value

A moins que les sessions soient désactivées, web2py, automatiquement, définit les cookies suivants et les utilise pour gérer les sessions :

response.cookies[response.session_id_name] = response.session_id
response.cookies[response.session_id_name]['path'] = "/"

Notez, que si une simple application inclut de multiples sous-domaines, et que vous souhaitez partager la session à travers ces sous-domaines (e.g., sub1.yourdomain.com, sub2.yourdomain.com, ...), vous devez définir explicitement le domaine du cookie de session tel que :

if not request.env.remote_addr in ['127.0.0.1', 'localhost']:
    response.cookies[response.session_id_name]['domain'] = ".yourdomain.com"

Les lignes ci-dessus peuvent être utiles si, par exemple, vous voulez autoriser l'utilisateur à rester connecté au travers de plusieurs sous-domaines.

Application init

init

Lorsque vous déployez web2py, vous souhaitez définir une application par défaut, i.e., l'application qui démarre lorsque vous entrez un chemin racine dans l'URL, telle que :

http://127.0.0.1:8000

Par défaut, lorsque web2py rencontre un chemin vide, il cherche une application appelée init. S'il n'y a pas d'application init, il cherche une application appelée welcome.

default_application

Le nom de l'application par défaut peut être changé de init à un autre nom en définissant default_application dans routes.py :

default_application = "myapp"

Note: default_application est apparu pour la première fois dans web2py version 1.83.

Voici quatre possibilités pour définir l'application par défaut :

  • Appelez votre application par défaut "init".
  • Définissez default_application vers le nom de votre application dans routes.py
  • Faites un lien symbolique depuis "applications/init" vers le répertoire de votre application.
  • Utilisez la réécriture d'URL comme présenté dans la section suivante.

Réécriture d'URL

url rewrite
routes_in
routes_out

web2py a la possibilité de réécrire l'URL d'une requête entrante avant d'appeler l'action du contrôleur (URL mapping), et inversement, web2py peut réécrire l'URL générée par la fonction URL (URL mapping inverse). Une raison de faire cela est de permettre la gestion des précédentes URLs (legacy), et également de simplifier les chemins en les rendant plus courts.

web2py inclut deux systèmes de réécriture d'URL distincts : un système clé en main parameter-based pour la plupart des cas d'usage, et un système flexible pattern-based pour les cas plus complexes. Pour spécifier les règles de réécriture d'URL, créez un nouveau fichier dans le répertoire "web2py" appelé routes.py (les contenus de routes.py dépendront du système de réécriture choisi, tel que décrit dans les deux prochaines sections). Les deux systèmes peuvent être mixés.

Notez que si vous éditez routes.py, vous devez recharger web2py. Ceci peut être fait de deux manières : en redémarrant le serveur web ou en cliquant sur le bouton de rechargement des routes dans l'interface d'administration. S'il y a un bug dans les routes, elles ne seront pas rechargées.

Système Parameter-based

Le routeur "parameter-based" fournit un accès simplifié à de nombreuses méthodes de réécriture d'URL embarquées. Ses possibilités incluent :

  • Oubli de l'application par défaut, du contrôleur et des noms de fonction avec des URLs visibles depuis l'extérieur (celles créées par la fonctionr URL())
  • Mapping de domaines (et/ou de ports) pour les applications et les contrôleurs
  • Embarquer un sélecteur de langue dans l'URL
  • Supprimer un préfixe fixé depuis les URLs entrantes et l'ajouter aux URLs sortantes
  • Mapping de fichiers racine tel que /robots.txt à un répertoire statique d'applications

Le routeur paramétrique fournit également une validation plus souple des URLs entrantes.

Imaginons que vous avez écrit une application appelée myapp et que vous souhaitez la rendre par défaut, afin que le nom de l'application ne soit pas dans l'URL comme vu par l'utilisateur. Votre contrôleur par défaut est toujours default, et vous voulez supprimer son nom des URLs visibles également. Voici ce qu'il faut mettre dans routes.py :

routers = dict(
  BASE  = dict(default_application='myapp'),
)

Voilà. Le routeur paramétrique est suffisamment intelligent pour savoir la meilleure chose à faire avec des URLs telles que :

http://domain.com/myapp/default/myapp

ou

http://domain.com/myapp/myapp/index

où le raccourci serait ambigu. Si vous avez deux applications, myapp et myapp2, vous obtiendrez le même effet, et en plus le contrôleur par défaut de myapp2 sera enlevée de l'URL à chaque fois qu'il est sûr (cas le plus fréquent, quasi tout le temps).

Voici un autre cas : supposons que vous voulez supporter les langues en se basant sur l'URL, où les URLs ressemblent à :

http://myapp/en/some/path

ou (réécrit)

http://en/some/path

Voici comment :

routers = dict(
  BASE  = dict(default_application='myapp'),
  myapp = dict(languages=['en', 'it', 'jp'], default_language='en'),
)

Maintenant, une URL rentrante telle que :

http:/domain.com/it/some/path

sera routée vers /myapp/some/path, et request.uri_language sera défini à 'it', afin que vous puissiez forcer la traduction. Vous pouvez également avoir des fichiers de langues statiques spécifiques.

http://domain.com/it/static/filename

sera mappé en:

applications/myapp/static/it/filename

si ce fichier existe. Sinon, les URLs ressembleront à :

http://domain.com/it/static/base.css

qui sera encore mappé en :

applications/myapp/static/base.css

(puisqu'il n'y a pas de static/it/base.css).

Afin que vous puissiez maintenant avoir des fichiers statiques de langue, incluant les images, si besoin est. Le mapping de domaine est supporté de telle sorte que :

routers = dict(
  BASE  = dict(
      domains = {
          'domain1.com' : 'app1',
          'domain2.com' : 'app2',
      }
  ),
)

fait ce que vous espériez.

routers = dict(
  BASE  = dict(
      domains = {
          'domain.com:80'  : 'app/insecure',
          'domain.com:443' : 'app/secure',
      }
  ),
)

mappe les accès à http://domain.com au contrôleur nommé insecure, tant que les accès HTTPS iront au contrôleur secure. Alternativement, vous pouvez mapper différents ports à différentes applications, de manière évidente.

Pour de plus amples informations, merci de consulter le fichier "routes.parametric.example.py" fourni dans le répertoire "example" de la distribution standard web2py.

Note : Le système parameter-based est apparu avec la version 1.92.1 de web2py.

Système Pattern-based

Bien que le système parameter-based qui vient d'être décrit devrait être suffisant pour la plupart des cas d'utilisation, l'alternative pattern-based fournit quelque flexibilité supplémentaire pour des cas plus complexes. Pour utiliser ce système, au lieu de définir des routeurs comme des dictionnaires de paramètres de routage, vous définissez 2 listes (ou tuples) de 2-tuples, routes_in et routes_out. Chaque tuple contient deux éléments : le pattern qui doit être remplacé et la chaîne qui le remplace. Par exemple :

routes_in = (
  ('/testme', '/examples/default/index'),
)
routes_out = (
  ('/examples/default/index', '/testme'),
)

Avec ces routes, l'URL :

http://127.0.0.1:8000/testme

est mappée en :

http://127.0.0.1:8000/examples/default/index

Pour le visiteur, tous les liens vers la page URL ressemblent à /testme.

Les patterns ont la même syntaxe que les expressions régulières en Python. Par exemple :

  ('.*.php', '/init/default/index'),

mappe toutes les URLs finissant en ".php" à la page d'index.

Le second terme d'une règle peut également être une redirection vers une autre page :

  ('.*.php', '303->http://example.com/newpage'),

Ici 303 est le code HTTP pour la réponse de redirection.

Parfois, vous voulez ignorer le préfixe d'application des URLs car vous ne souhaitez exposer qu'une seule application. Ceci peut être fait avec :

routes_in = (
  ('/(?P<any>.*)', '/init/\g<any>'),
)
routes_out = (
  ('/init/(?P<any>.*)', '/\g<any>'),
)

Il y a aussi une syntaxe alternative qui peut être mixée avec la notation en expression régulière ci-dessus. Ceci consiste à utiliser $name au lieu de (?P<name>\w+) ou \g<name>. Par exemple :

routes_in = (
  ('/$c/$f', '/init/$c/$f'),
)

routes_out = (
  ('/init/$c/$f', '/$c/$f'),
)

éliminerait aussi le préfixe d'application "/example" dans toutes les URLs.

En utilisant la notation $name, vous pouvez automatiquement mapper routes_in en routes_out, supposant que vous n'utilisez aucune expression régulière. Par exemple :

routes_in = (
  ('/$c/$f', '/init/$c/$f'),
)

routes_out = [(x, y) for (y, x) in routes_in]

S'il y a plusieurs routes, la première à correspondre à l'URL est exécutée. S'il n'y a pas de correspondance, le chemin reste inchangé.

Vous pouvez utiliser $anything pour faire correspondre n'importe quoi (.*) jusqu'à la fin de la ligne.

Voici un version minimale de "routes.py" pour gérer le favicon et les requêtes de robots :

favicon
robots

routes_in = (
  ('/favicon.ico', '/examples/static/favicon.ico'),
  ('/robots.txt', '/examples/static/robots.txt'),
)
routes_out = ()

Voici un exemple plus comlpexe qui expose une simple application "myapp" sans préfixe non nécessaire mais qui expose aussi admin, appadmin et static:

routes_in = (
  ('/admin/$anything', '/admin/$anything'),
  ('/static/$anything', '/myapp/static/$anything'),
  ('/appadmin/$anything', '/myapp/appadmin/$anything'),
  ('/favicon.ico', '/myapp/static/favicon.ico'),
  ('/robots.txt', '/myapp/static/robots.txt'),
)
routes_out = [(x, y) for (y, x) in routes_in[:-2]]

La syntaxe générale pour les routes est plus complexe que les simples exemples que nous avons vu jusqu'ici. Voici un exemple plus général et représentatif :

routes_in = (
 ('140.191.\d+.\d+:https?://www.web2py.com:post /(?P<any>.*).php',
  '/test/default/index?vars=\g<any>'),
)

Ceci mappe les requêtes POST http ou https (notez les minuscules "post") pour l'hôte www.web2py.com depuis une IP distante correspondant à l'expression régulière

'140.191.\d+.\d+'

demandant une page correspondant à l'expression régulière

'/(?P<any>.*).php'

en

'/test/default/index?vars=\g<any>'

\g<any> est remplacé par l'expression régulière correspondante.

La syntaxe générale est

'[remote address]:[protocol]://[host]:[method] [path]'

Si la première section du pattern (tout sauf [path]) est manquant, web2py fournit par défaut :

'.*?:https?://[^:/]+:[a-z]+'

L'expression entière est utilisée comme une expression régulière, donc "." doit être échappé et toute autre sous-expression correspondante peut être capturée en utilisant (?P<...>...) avec la syntaxe de regex Python. La méthode de requête (typiquement GET ou POST) doit être en minuscules. L'URL correspondante a eu tout %xx échappé sans guillemets.

Ceci permet de rerouter les requêtes en se basant sur l'UP du client ou du domaine, selon le type de requête, sur la méthode, et le chemin. Ceci permet également à web2py de mapper différents hôtes virtuels en différente applications. Toute sous-expression correspondante peut être utilisée pour construire l'URL cible et, éventuellement, être transmis en variable GET.

Tous les serveurs web principaux, tel que Apache et lighttpd, ont également la possibilité de réécrire des URLs. Dans un environnement de production, ce peut être une option en lieu et place de routes.py. Peu importe ce que vous décidez, nous vous recommandons fortement de ne pas entrer en dur les URLs internes dans votre application et d'utiliser la fonction URL pour les générer. Ceci rendra votre application plus portable dans le cas où les routes devaient changer.

Réécriture d'URL spécifique à une application
routes_app

Lorsque l'on utilise le système pattern-based, une application peut définir ses propres routes dans un fichier spécifique à l'application routes.py, se trouvant dans le répertoire de base des applications. Il est activé en configurant routes_app dans le routes.py de base pour déterminer depuis une URL entrante le nom de l'application à sélectionner. Lorsque ceci arrive, le fichier routes.py spécifique à l'application est utilisé à la place de celui de base.

Le format de routes_app est identique à routes_in, excepté que le pattern de remplacement est simplement le nom de l'application. Si l'application routes_app à l'URL entrante ne résulte en rien dans le nom de l'application, ou aucun routes.py spécifique à l'application n'est trouvé, le routes.py est utilisé comme d'habitude.

Note : routes_app est apparu avec la version 1.83 de web2py.

Application par défaut, contrôleur et fonction
default_application
default_controller
default_function

Lorsque le système pattern-based est utilisé, le nom de l'application par défaut, le contrôleur et la fonction peut être changés depuis init, default, et index respectivement vers un autre nom en définissant la valeur appropriée dans routes.py :

default_application = "myapp"
default_controller = "admin"
default_function = "start"

Note: Ces objets sont apparus avec la version 1.83 de web2py.

Routes en erreur

routes_onerror

Vous pouvez également utiliser routes.py pour re-router les requêtes vers des actions spéciales dans le cas où il y a une erreur sur le serveur. Vous pouvez spécifier ce mapping de manière globale, pour chaque application, pour chaque code d'erreur, ou pour chaque application et code d'erreur. Voici un exemple :

routes_onerror = [
  ('init/400', '/init/default/login'),
  ('init/*', '/init/static/fail.html'),
  ('*/404', '/init/static/cantfind.html'),
  ('*/*', '/init/error/index')
]

Pour chaque tuple, la première chaine est mise en correspondance avec "[app name]/[error code]". Si une correspondance est trouvée, la requête qui échoue est re-routée vers l'URL dans la seconde chaîne du tuple correspondant. Si l'erreur gérant l'URL n'est pas un fichier statique, les variables GET suivantes seront transférées à l'action de l'erreur :

  • code: le code HTTP (e.g., 404, 500)
  • ticket: sous la forme de "[app name]/[ticket number]" (ou "None" s'il n'y a pas de ticket)
  • requested_uri: équivalent à request.env.request_uri
  • request_url: équivalent à request.url

Ces variables seront accessibles par l'action de gestion des erreurs via request.vars et peut être utilisée pour générer la réponse à l'erreur. En particulier, il est de bonne pratique pour l'action d'erreur de retourner le code d'erreur HTTP original au lieu du code par défaut 200 (OK). Ceci peut être fait en définissant response.status = request.vars.code. Il est également possible d'avoir l'action d'erreur qui envoie (ou mettre en file d'attente) un email à l'administrateur, incluant un lien vers le ticket dans l'admin.

Les erreurs sans correspondance affichent une page d'erreur par défaut. Cette page d'erreur par défaut peut aussi être personnalisée (voir "routes.parametric.example.py" et "routes.patterns.example.py" dans le dossier "examples") :

error_message = '<html><body><h1>%s</h1></body></html>'
error_message_ticket = '''<html><body><h1>Internal error</h1>
     Ticket issued: <a href="/admin/default/ticket/%(ticket)s"
     target="_blank">%(ticket)s</a></body></html>'''

La première variable contient le message d'erreur lorsqu'une application invalide ou une fonction est demandée. la seconde variable contient le message d'erreur lorsqu'un ticket est généré.

routes_onerror work with both routing mechanisms.

error_handler

Dans "routes.py" vous pouvez également spécifier une action en charge de la gestion d'erreurs :

error_handler = dict(application='error',
                      controller='default',
                      function='index')

Si l'error_handler est spécifié, l'action est appelée sans redirection utilisateur et l'action de gestion sera en charge de gérer l'erreur. Dans le cas où la page de gestion d'erreur retourne elle-même une erreur, web2py reviendra à ses anciennes réponses statiques.

Gestion des actifs statiques

Depuis la version 2.1.0, web2py a la possibilité de gérer les actifs statiques.

Lorsqu'une application est en développement, un fichier statique peut souvent changer, donc web2py envoie les fichiers statiques sans en-têtes entraînant le cache. Ceci a pour effet de bord de "forcer" le navigateur à envoyer ses requêtes de fichiers statiques à chaque requête. Ceci résulte en de plsu faibles performance lors du chargement d'une page.

Sur un site de "production", vous pouvez vouloir servir les fichiers statiques avec des en-têtes gérant le cache afin de vous prémunir de téléchargements inutiles puisque les fichiers statiques sont inchangés.

Les en-têtes cache autorisent le navigateur à conserver chaque fichier une fois, économisant ainsi de la bande passante et réduisant les temps de chargement.

Maintenant, il y a un problème : qu'est-ce que le cache devrait déclarer comme en-têtes ? A quel moment les fichiers devraient-ils expirer ? Lorsque les fichiers sont fournis pour la première fois, le serveur ne peut pas connaître leur prochain changement.

Une approche manuelle consiste à la création de sous-dossiers pour différentes versions des fichiers statiques. Par exemple, une précédente version de "layout.css" peut être disponible à l'URL "/myapp/static/css/1.2.3/layout.css". Lorsque vous changez le fichier, vous créez un nouveau sous-dossier et vous liez le fichier en tant que "/myapp/static/css/1.2.4/layout.css".

Cette procédure fonctionne mais est lourde puisqu'à chaque modification du fichier css, vous devez vous souvenir de le déplacer vers un autre dossier, changer l'URL du fichier dans le layout.html et déployer.

La gestion des actifs statiques résoud le problème en autorisant le développeur à déclarer une version pour un groupe de fichiers statiques et qui ne seront alors requêtés uniquement lorsque le numéro de version change. Le numéro de version fait partie de l'URL du fichier comme dans l'exemple précedent. La différence de l'approche précédente est que ce numéro de version n'apparaît que dans l'URL et non dans le système de fichiers.

Si vous voulez fournir "/myapp/static/layout.css" avec les en-têtes de cache, vous avez juste besoin d'inclure le fichier avec une URL différence qui inclut le numéro de version :

/myapp/static/_1.2.3/layout.css

(notez que l'URL définit un numéro de version, qui n'apparaît nulle part ailleurs).

Notez que l'URL commence avec "/myapp/static/", suivie par le numéro de version composé d'un underscore et de 3 entiers séparés par des points (tel que décrit dans SemVer), et ensuite par le nom de fichier. Notez également que vous n'avez pas à créer un répertoire "_1.2.3/".

A chaque fois qu'un fichier statique est demandé avec une version dans l'URL, il sera fourni avec des en-têtes de cache "loin dans le futur", particulièrement :

Cache-Control : max-age=315360000
Expires: Thu, 31 Dec 2037 23:59:59 GMT

Ceci signifie que le navigateur ne récupèrera ces fichiers qu'une seule fois, et ils seront sauvés "pour toujours" dans le cache du navigateur.

Chaque fois que "_1.2.3/filename" est demandé, web2py supprimera la version du chemin et fournira le fichier avec les en-têtes loin dans le futur afin qu'ils soient mis en cache pour toujours. Si vous avez changé le numéro de version dans l'URL, ceci indiquera au navigateur qu'il doit demander un fichier différent et le fichier sera alors récupéré.

Vous pouvez utiliser "_1.2.3", "_0.0.0", "_999.888.888", tant que le numéro de version commence avec un underscore suivi par 3 nombres séparés par des points.

Lorsqu'en développement, vous pouvez utiliser response.files.append(...) pour lier les URLs statiques de fichiers statiques. Dans ce cas, vous pouvez inclure la partie "_1.2.3/" manuellement, ou vous profitez d'un nouveau paramètre de l'objet de réponse : response.static_version. Incluez juste les fichiers comme vous en avez l'habitude, par exemple

{{response.files.append(URL('static','layout.css'))}}

et définissez dans les modèles

response.static_version = '1.2.3'

Ceci réécrira automatiquement toute URL "/myapp/static/layout.css" en tant que "/myapp/static/_1.2.3/layout.css", pour tout fichier inclus dans response.files.

Souvent en production vous laissez le serveur web (apache, nginx, etc.) fournir les fichiers statiques. Vous avez besoin d'ajuster votre configuration dans un tel cas qui "évitera" la partie "_1.2.3/".

Par exemple, sous Apache, changez :

AliasMatch ^/([^/]+)/static/(.*)    /home/www-data/web2py/applications/$1/static/$2

en :

AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*)    /home/www-data/web2py/applications/$1/static/$2

De la même manière, sous Nginx, changez :

location ~* /(\w+)/static/ {
    root /home/www-data/web2py/applications/;
    expires max;
}

en :

location ~* /(\w+)/static(?:/_[\d]+.[\d]+.[\d]+)?/(.*)$ {
   alias /home/www-data/web2py/applications/$1/static/$2;
   expires max;
}

Démarrer des tâches en arrière-plan

Dans web2py, toute requête HTTP est desservie dans son propre thread. Les threads sont recyclés pour efficacité et gérés par le serveur web. Par sécurité, le serveur web définit un timeout sur chaque requête. Ceci signifie que les actions ne devraient pas démarrer de tâches aussi longues, ne devraient pas créer de nouveaux threads, et ne devraient pas créer de processus fils (c'est possible mais non recommandé).

Le meilleur moyen de démarrer des tâches longues est de les faire tourner en arrière-plan. Il n'y a pas un seul moyen de faire cela, mais nous décrivons ici 3 mécanismes qui sont pré-construits dans web2py: cron, homemade task queues, et scheduler.

Par cron nous nous référons à une fonctionnalité web2py et non un mécanisme Cron Unix. Le cron web2py fonctionne également sur Windows.

Le cron web2py est le meilleur moyen si vous avez besoin d'exécuter des tâches en arrière-plan à des heures planifiées et ces tâches prennent une durée relativement courtes comparée à l'intervalle entre deux appels. Chaque tâche démarre dans son propre processus, et de multiples tâches peuvent s'exécuter de manière concurrente, mais vous n'avez aucun contrôle sur le nombre de tâches qui s'exécutent. Si par accident une tâche s'écrase elle-même, ceci peut causer un verrou sur la base de données et entraîner une surcharge mémoire.

Le scheduler web2py apporte une approche différente. Le nombre de processus en cours est fixé, et peuvent s'exécuter sur différentes machines. Chaque process est appelé un worker. Chaque worker récupère une tâche dès qu'il y a de la disponibilité et l'exécute dès que possible après l'heure planifiée, mais pas nécessairement à l'heure exacte. Il ne peut pas y avoir plus de processus en cours que le nombre de tâches planifiées et donc aucun pic de mémoire. Les tâches planifiées peuvent être définies dans les modèles et être stockées en base de données. Le scheduler web2py n'implémente pas de système de queue distribuée puisqu'il suppose que le temps de distribution des tâches est négligeable comparé au temps d'exécution des tâches. Les workers récupèrent leur tâche depuis la base de données.

Les tâches en queue "maison" peuvent être une alternative plus simple au scheduler web2py dans certains cas.

Cron

cron

Le cron web2py fournit la possibilité pour les application d'exécuter des tâches à des heures pré-déterminées, dans une plateforme indépendante.

Pour chaque application, la fonctionnalité de cron est définie par un fichier crontab :

app/cron/crontab

Il suit la syntaxe définie en référence [cron] (avec certaines extensions qui sont spécifiques à web2py).

Avant web2py 2.1.1, cron était activé par défaut et pouvait être désactivé avec l'option -N sur la ligne de commande. Depuis la version 2.1.1, cron est désactivé par défaut et peut être activé avec l'option -Y. Ce changement a été motivé par le désir de pousser les utilisateurs à utiliser le nouveau scheduler (qui est plus avancé que le mécanisme de cron) et également car le cron peut impacter les performances.

Ceci signifie que toute application peut avoir une configuration cron séparée et cette configuration peut être changée à l'intérieur de web2py sans affecter l'OS lui-même.

Voici un exemple :

0-59/1  *  *  *  *  root python /path/to/python/script.py
30      3  *  *  *  root *applications/admin/cron/db_vacuum.py
*/30    *  *  *  *  root **applications/admin/cron/something.py
@reboot root    *mycontroller/myfunction
@hourly root    *applications/admin/cron/expire_sessions.py

Les deux dernières lignes dans cet exemple utilisent des extensions à la syntaxe cron habituelle pour fournir des fonctionnalités supplémentaires à web2py.

Le fichier "applications/admin/cron/expire_sessions.py" existe en fait et est livré avec l'application admin. Il vérifie les sessions expirées et les supprime. "applications/admin/cron/crontab" démarre cette tâche toutes les heures.

Si la tâche/script est préfixé avec une astérisque (*) et finit avec .py, il sera exécuté automatiquement dans l'environnement web2py. Cela signifie que vous aurez tous les contrôleurs et modèles à votre disposition. Si vous utilisez 2 asrésiques (**), les modèles ne seront pas exécutés. C'est le meilleur moyen de les utiliser, puisqu'il y a un minimum de surcharge evitant ainsi d'éventuels problèmes de verrou.

Notez que les scripts/fonctions exécutées dans l'environnement web2py nécessite un db.commit() manuel à la fin de la fonction pour que la transaction ne soit pas annulée.

web2py ne génère pas de tickets ou de tracebacks explicatifs en mode shell, ce qui est le fonctionnement du cron, donc assuez-vous qeu votre code web2py s'exécute sans erreur avant de le définir comme tâche cron étant donné que vous ne serez pas en mesure de voir ces erreurs lorsque l'exécution se fera depuis le cron. De plus, soyez attentifs à la manière dont vous utilisez les modèles : lorsqu'une exécution se produit dans un process séparé, les verrous à la base de données doivent être pris en compte afin d'éviter les pages d'attendre les tâches cron qui pourraient bloquer la base de données. Utilisez la syntaxe ** si vous n'avez pas besoin d'utiliser la base de données dans votre tâche cron.

Vous pouvez également appeler une fonction contrôleur, auquel cas il n'y a pas besoin de spécifier un chemin. Le contrôleur et la fonction seront ceux de l'application appelante. Faites particulièrement attention aux failles listées ci-dessus. Par exemple :

*/30  *  *  *  *  root *mycontroller/myfunction

Si vous spécifiez @reboot dans le premier champ du fichier crontab, la tâche indiquée sera exécutée uniquement une seule fois, au démarrage de web2py. Vous pouvez utiliser cette solution si vous voulez pré-cacher, vérifier, ou initialiser des données pour une application au démarrage de web2py. Notez que les tâches cron sont exécutées en parallèle avec l'application --- si l'application n'est pas prête à servir des requêtes jusqu'à ce que la tâche cron soit finie, vous devriez implémenter des vérifications pour y remédier. Exemple :

@reboot  root *mycontroller/myfunction

Selon la manière dont vous invoquez web2py, il y a 4 modes opératoires pour le cron web2py.

  • soft cron: disponible pour tous les modes d'exécution
  • hard cron: disponible si le serveur web pré-packagé est utilisé (aussi bien directement que via le module mod_proxy d'Apache)
  • external cron: disponible si vous avez accès au propre service cron du système
  • Aucun cron

Par défaut, si vous utilisez le serveur web fourni c'est le "hard cron" ; dans tous les autres cas, ce sera le "soft cron" par défaut. "Soft cron" est la méthode par défaut si vous utilisez CGI, FASTCGI ou WSGI (mais notez que le soft cron n'est pas enabled par défaut dans le fichier standard wsgihandler.py fourni par web2py).

Vos tâches seront exécutées au premier appel (chargement de la page) ) web2py après le temps spécifié dans le crontab ; mais seulement après avoir chargé la page, donc aucun délai de sera observable par l'utilisateur. Evidemment, il y a une certaine incertitude sur l'exécution précise de la tâche, selon le trafic reçu par le site. La tâche cron peut également être interrompue si le serveur web a un timeout de chargement de page défini. Si ces limitations ne sont pas acceptables, voir external cron. Soft cron est un dernier ressort raisonnable, mais votre serveur web autorise d'autres méthodes cron, ils devraient préférer le soft cron.

Hard cron est le défaut si vous utilisez le serveur web pré-packagé (soit directement soit le mod_proxy d'Apache). Hard cron est exécuté sur un thread parallèle, contrairement au soft cron, il n'y a aucune limitation sur la précision de l'heure de démarrage ou d'exécution.

Le cron externe n'est par défaut dans aucun scénario, mais nécessite que vous ayez accès aux fonctionnalités cron du système. Il fonctionne sur un processus parallèle, donc aucune limitation du soft cron ne s'applique. C'est le meilleur moyen d'exécuter du cron sous WSGI ou FASTCGI.

Exemple de ligne à ajouter au système crontab, (habituellement /etc/crontab) :

0-59/1 * * * * web2py cd /var/www/web2py/ && python web2py.py -J -C -D 1 >> /tmp/cron.output 2>&1

Avec le cron externe, assurez vous d'ajouter soit -J (ou -cronjob, qui revient au même) comme indiqué au-dessus afin que web2py sache que la tâche est exécutée par le cron. Web2py définit ceci de manière interne avec le soft et le hard cron.

File d'attente de tâches maison

Alors que cron est utile pour démarrer des tâches à des intervalles de temps réguliers, il n'est pas toujours la meilleure solution pour exécuter une tâche en arrière-plan. Pour cet usage, web2py fournit la possibilité d'exécuter n'importe quel script Python comme s'il était lancé à l'intérieur d'un contrôleur :

python web2py.py -S app -M -R applications/app/private/myscript.py -A a b c

-S app indique à web2py de lancer "myscript.py" en tant que "app", -M indique d'exécuter les modèles, et -A a b c passe des arguments optionnels à la ligne de commande sys.args=['a','b','c'] à "myscript.py".

Ce type de processus en arrière-plan ne devrait pas être exécuté via le cron (sauf éventuellement pour le cron @reboot) puisque vous avez besoin de vous assurer qu'il n'y a pas plus d'une instance qui fonctionne au même moment. Avec cron, il est possible qu'un processus démarre à l'itération 1 du cron et ne soit pas terminée pour l'itération 2 du cron, alors le cron démarre encore, et encore, et encore - saturant ainsi le serveur mail.

Dans le chapitre 8, nous fournirons un exemple d'usage pour la méthode ci-dessus pour envoyer des mails.

Ordonnanceur web2py

La principale solution pour démarrer des tâches en arrière-plan avec web2py (et donc en s'éloignant du processus du serveur web) est l'ordonnanceur embarqué.

L'API stable est composée de ces fonctions :

  • disable()
  • resume()
  • terminate()
  • kill()
  • queue_task(),
  • task_status()
  • stop_task()

L'ordonnanceur web2py fonctionne quasiment à l'identique de la file d'attente de tâches décrite dans la sous-section précédente avec quelques différences :

  • Il fournit un mécanisme standard pour créer, ordonnancer, et superviser les tâches.
  • Il n'y a pas un simple processus d'arrière-plan mais un ensemble de processus workers.
  • Le travail des noeuds du worker peuvent être supervisés car leur état, comme l'état des tâches, est stocké dans la base de données.
  • Il fonctionne sans web2py mais ceci n'est pas documenté ici.

L'ordonnanceur n'utilise pas cron, bien que l'on puisse utiliser la syntaxe cron @reboot pour démarrer les noeuds du worker.

Plus d'informations sur le déploiement de l'ordonnanceur sous Linux et Windows sont dans le chapitre sur les déploiements.

Dans l'ordonnanceur, une tâche est simplement une fonction définie dans un modèle (ou dans un module et importée dans un modèle). Par exemple :

def task_add(a,b):
    return a+b

Les tâches seront toujours appelées dans le même environnement vu par les contrôleurs et qui voient toutes les variables globales définies dans les modèles, incluant les connexions à la base de donnée (db). Les tâches diffèrent d'une action de contrôleur puisqu'elles ne sont pas associées à une requête HTTP et il n'y a donc pas de request.env. Aussi, les tâches peuvent accéder à une variable d'un autre environnement qui n'est pas présent dans des requêtes normales : W2P_TASK. W2P_TASK.id contiennent le scheduler_task.id et W2P_TASK.uuid le champ scheduler_task.uuid de la tâche en cours d'exécution.

N'oubliez pas d'appeler db.commit() à la fin de chaque tâche si il y a des inserts/updates dans la base de données. web2py valide par défaut à la fin d'une action réussie mais les tâches de l'ordonnanceur ne sont pas des actions.

Pour activer l'ordonnanceur, vous devez instancier la classe Scheduler dans un modèle. Le meilleur moyen d'activer l'ordonnanceur dans votre application est de créer un fichier modèle appelé scheduler.py et de définir votre fonction dedans. Après les fonctions, vous pouvez mettre le code suivant dans votre modèle :

from gluon.scheduler import Scheduler
scheduler = Scheduler(db)

Si vos tâches sont définies dans un module (au contraire d'un modèle) vous pouvez avoir à redémarrer les workers.

La tâche est planifiée avec

scheduler.queue_task(task_add,pvars=dict(a=1,b=2))

Paramètres

Le premier argument de la classe Scheduler doit être la base de données qui doit être utilisée par l'ordonnanceur pour communiquer avec les workers. Ceci peut être le db de l'application ou un autre db dédié, peut-être partagé par plusieurs applications. Si vous utilisez SQLite, il est recommandé d'utiliser une base séparée de celle utilisée par votre application afin de conserver une application réactive. Une fois les tâches définies et que le Scheduler est instancié, tout ce qu'il reste à faire est de démarrer les workers. Vous pouvez le faire de différentes manières :

python web2py.py -K myapp

démarre un worker pour l'application myapp. Si vous voulez démarrer plusieurs workers pour la même application, vous pouvez le faire en passant uniquement myapp,myapp`. Vous pouvez également passer group_names (écrasant celui défini dans votre modèle) avec python web2py.py -K myapp:group1:group2,myotherapp:group1 Si vous avez un modèle appelé scheduler.py vous pouvez démarrer/arrêter les workers depuis la fenêtre par défaut de web2py (celle que vous avez utilisée pour définir l'adresse IP et le port). #### Déploiement de l'ordonnanceur Une dernière belle addition : si vous utilisez le serveur web embarqué, vous pouvez démarrer le serveur web et l'ordonnanceur avec une seule ligne de code (ceci suppose que vous ne voulez que la fenêtre web2py s'ouvre, sinon vous pouvez utiliser le menu "Schedulers" à la place) python web2py.py -a yourpass -K myapp -X

Vous pouvez passer les paramètres habituels (-i, -p, ici -a évite à la fenêtre d'apparaître), passer n'importe quelle application avec le paramètre -K et ajoutez un -X. L'ordonnanceur démarrera avec le serveur web !

Les utilisateurs Windows cherchant à créer un service devraient voir le chapitre "Possibilités de déploiement".


[[scheduler_signature]]
#### Signature complète de l'ordonnanceur
La signature complète de l'ordonnanceur est la suivante : 

Scheduler( db, tasks=None, migrate=True, worker_name=None, group_names=None, heartbeat=HEARTBEAT, max_empty_runs=0, discard_results=False, utc_time=False ) :code Observons les en détail : - db est l'instance DAL de la base de données où vous souhaitez que les tables du scheduler soient placées. - tasks est un dictionnaire qui mappe les noms de tâche avec des fonctions. Si vous ne passez pas ce paramètre, la fonction sera cherchée dans l'environnement de l'application. - worker_name est None par défaut. Dès lors que le worker est démarré, un nom de worker est généré comme un hostname-uuid. Si vous souhaitez spécifier cela, assurez-vous qu'ils soit unique. - group_names est défini par défaut à **[main]**. Toutes les tâches ont un paramètre group_name, défini à **main** par défaut. Les Workers peuvent uniquement récupérer les tâches assignées à leur groupe. ------ NB: Ceci est utile si vous avez différentes instances de workers (e.g. sur différentes machines) et que vous souhaitez assigner les tâches à un worker spécifique. NB2: Il est possible d'assigner un worker à plusieurs groupes, et ils peuvent également tous appartenir au même, tel que ['mygroup','mygroup']. Les tâches seront distribuées en prenant en considération qu'un worker avec pour group_names ['mygroup','mygroup'] peut exécuter le double de tâches qu'un worker avec pour group_names [mygroup]. ------ - heartbeat est par défaut défini à 3 secondes. Ce paramètre est celui qui va contrôler le nombre de fois où le scheduler va vérifier son statut dans la table scheduler_worker et voir s'il y a des tâches **ASSIGNED** à lui-même à exécuter. - max_empty_runs est à 0 par défaut, ce qui signifie que le worker continuera d'exécuter les tâches tant qu'elles sont **ASSIGNED**. Si vous le définissez à une valeur de, disons, 10, un worker va mourir automatiquement si il est **ACTIVE** et aucune tâche ne lui sera **ASSIGNED** pour 10 boucles. Une loupe est lorsqu'un worker recherche des tâches, toutes les 3 secondes (ou la valeur définie dans heartbeat). - discard_results est False par défaut. Si défini à True, aucun enregistrement scheduler_run ne sera créé. ------ NB : Les enregistrements scheduler_run seront créés comme précédemment pour les statuts de tâches **FAILED**, **TIMEOUT** et **STOPPED**. ------ - utc_time est False par défaut. Si vous avez besoin de coordonner les workers sur différentes timezones, ou que vous n'avez pas de problèmes avec les temps Solar/DST, fournir des datetimes de différents pays, etc, vous pouvez le définir à True. L'ordonnanceur honorera le temps UTC et fonctionnera en laissant le temps local de côté. Faille : vous avez besoin d'ordonnancer vos tâches avec des temps UTC (pour start_time, stop_time et ainsi de suite). Maintenant nous avons l'infrastructure en place : défini les tâches, renseigné l'ordonnanceur à leur propos, démarré les worker(s). La seule chose qu'il reste à faire est de planifier les tâches. #### Tâches Les tâches peuvent être planifiées en programmation ou via appadmin. En fait, une tâche est planifiée simplement en ajoutant une entrée dans la table "scheduler_task" à laquelle vous pouvez accéder via appadmin : http://127.0.0.1:8000/myapp/appadmin/insert/db/scheduler_task


La signification des champs dans cette table est évidente. Les champs "args" et "vars" sont les valeurs à passer à la tâche en format JSON. Dans le cas du "task_add" ci-dessus, un exemple de "args" et "vars" pourrait être :

args = [3, 4] vars = {}

:code ou

args = [] vars = {'a':3, 'b':4} :code La table scheduler_task est celle où les tâches sont organisées. Pour ajouter des tâches via l'API, utilisez scheduler.queue_task('mytask',...) qui est documenté [[below #queue_task_sig]] . #### Cycle de vie d'une tâche Toutes les tâches suivent un cycle de vie [[scheduler tasks https://www.web2py.com/books/default/image/38/ce8edcc3.png center]] Par défaut, lorsque vous envoyez une tâche au scheduler, il est en statut **QUEUED**. Si vous avez besoin de l'exécuter plus tard, utilisez le paramètre start_time (défaut = maintenant). Si pour certaines raisons vous avez besoin d'être sûr que la tâche ne s'exécutera pas après un certain horaire (peut être une requête vers un service web qui s'éteint après 1am, un mail qui nécessite de ne pas être envoyé après les heures ouvrées, ...) vous pouvez définir pour cela un stop_time (défaut = None). Si votre tâche n'est pas récupérée par un worker avant stop_time, elle sera définie à **EXPIRED**. Les tâches sans stop_time défini ou récupérées **BEFORE** stop_time sont **ASSIGNED** à un worker. Lorsqu'un worker récupère une tâche, son statut est défini à **RUNNING**. Les tâches **RUNNING** peuvent finir en : - **TIMEOUT** lorsque plus que n secondes sont passées avec un paramètre timeout (par défaut à 60 secondes). - **FAILED** lorsqu'une exception est détectée, - **COMPLETED** lorsqu'elles réussissent complètement. Les valeurs pour start_time et stop_time devraient être des objets datetime. Pour planifier un démarrage "mytask" 30 secondes après maintenant, par exemple, vous feriez : from datetime import timedelta as timed scheduler.queue_task('mytask', start_time=request.now + timed(seconds=30)) :code De plus, vous pouvez contrôler le nombre de fois où une tâche doit être répétée (i.e. vous avez besoin d'agréger certaines données à des intervalles spécifiés). Pour faire cela, définissez le paramètre repeats (défaut à 1 fois seulement, 0 = illimité). Vous pouvez influencer le nombre de secondes qui devraient passer entre les exécutions avec le paramètre période (défaut = 60 secondes). ------ Le comportement par défaut : la période de temps n'est pas calculée entre la END du premier tour et le START du suivant, mais depuis le temps START du premier tour au temps START du cycle suivant. Ceci peut causer une accumulation de 'drift' dans l'heure de début du job. Après la v2.8.2, un nouveau paramètre prevent_drift a été ajouté, par défaut à False. Si défini à True lorsqu'une tâche est mis en queue, le paramètre start_time prendra le dessus sur la période, pour éviter une dérive. ------ Vous pouvez aussi définir combien de fois la fonction peut lever une exception (i.e. demander des données depuis un web service lent) et être mise en queue à nouveau plutôt que de l'arrêter en statut **FAILED** en utilisant le paramètre retry_failed (par défaut à 0, -1 = illimité). [[task repeats https://www.web2py.com/books/default/image/38/7d8b85e4.png center]] En résumé : vous avez - period et repeats pour obtenir une replanification automatique de fonction - timeout pour vous assurer qu'une fonction n'excède pas un certain temps - retry_failed pour contrôler combien de fois la tâche peut "échouer" - start_time et stop_time pour planifier une fonction dans une timeframe restreinte #### queue_task [[queue_task_sig]] La méthode : scheduler.queue_task( function, pargs=[], pvars={}, start_time=now, #datetime stop_time = None, #datetime timeout = 60, #seconds prevent_drift=False, period=60, #seconds immediate=False, repeats = 1 ) :code vous permet de mettre en file d'attente des tâches qui devront être exécutées par des workers. Il retourne une ligne (voir [[here #queue_task_return]]), et il prend les paramètres suivants : - function (requis) : Ce peut être un nom de tâche ou une référence vers une fonction actuelle. - pargs: sont les arguments qui doivent être passés à la tâche, stockés dans une liste Python. - pvars : sont les arguments nommés qui doivent être passés à la tâche, stockés dans un dictionnaire Python - toutes les autre colonnes scheduler_task peuvent être passées comme des arguments mots-clés ; les plus importants sont présentés. Par exemple : scheduler.queue_task('demo1', [1,2])

 
fait exactement la même chose que 

scheduler.queue_task('demo1', pvars={'a':1, 'b':2})

:code que

st.validate_and_insert(function_name='demo1', args=json.dumps([1,2]))

:code et que :

st.validate_and_insert(function_name='demo1', vars=json.dumps({'a':1,'b':2}))

:code Voici un exemple complet plus complexe :

def task_add(a,b): return a+b

scheduler = Scheduler(db, tasks=dict(demo1=task_add))

scheduler.queue_task('demo1', pvars=dict(a=1,b=2), repeats = 0, period = 180) :code Depuis la version 2.4.1 si vous passez un paramètre additionnel immediate=True il forcera le worker principal à réassigner les tâches. Depuis 2.4.1, le worker vérifie les nouvelles tâches tous les 5 cycles (soit, 5*heartbeats secondes). Si vous aviez une application qui ait besoin de vérifier plus fréquemment les nouvelles tâches, vous deviez utilisez un comportement ''snappy'' en forçant la diminution du paramètre heartbeat, mettant ainsi la base sous pression sans raison. Avec immediate=True vous pouvez forcer cette vérification pour des nouvelles tâches : cela arrivera au plus tard à heartbeat secondes écoulées. Un appel à scheduler.queue_task retourne la tâche id et uuid de la tâche mise en file (peut être celle que vous avez passée ou une autre générée automatiquement), et les errors possibles : [[queue_task_return]] <Row {'errors': {}, 'id': 1, 'uuid': '08e6433a-cf07-4cea-a4cb-01f16ae5f414'}>

 
S'il y a des erreurs (habituellement une erreur de syntaxe ou des erreurs de validation d'entrée),
vous obtiendrez le résultat de la validation, et id et uuid seront à None.

<Row {'errors': {'period': 'enter an integer greater than or equal to 0'}, 'id': None, 'uuid': None}> #### task_status [[task_status]] Pour requêter l'ordonnanceur au sujet des tâches, utilisez task_status

scheduler.task_status(ref, output=False) :code L'argument ref peut être - integer --> la vérification sera faite par scheduler_task.id - string --> la vérification sera faite par scheduler_task.uuid - query --> la vérification comme vous le souhaitez (comme dans db.scheduler_task.task_name == 'test1')output=True récupère l'enregistrement du scheduler_run Il retourne un simple objet Row, pour la tâche la plus récente correspondante aux critères. L'enregistrement scheduler_run est récupéré par un left join, et peut donc avoir tous les champs à None ##### Exemple : récupérer le statut des tâches ordonnancées, les résultats et les tracebacks Ici, l'instance du scheduler est mysched
task = mysched.queue_task(f, ....) task_status = mysched.task_status(task.id, output=True) traceback = task_status.scheduler_run.traceback result = task_status.scheduler_run.run_result #or result = task_status.result

:code #### Resultats et sortie La table "scheduler_run" stocke le statut de toutes les tâches en cours d'exécution. Chaque enregistrement référence une tâche qui a été récupérée par un worker. Une tâche peut avoir plusieurs exécutions. Par exemple, une tâche planifiée pour être répétée 10 fois par heure aura probablement 10 exécutions (à moins que l'une échoue ou qu'elles prennent plus qu'une heure). Attention au fait que si la tâche ne retourne aucune valeur, elle est retirée de la table scheduler_run dès qu'elle est terminée. Les statuts d'exécution possibles sont :

RUNNING, COMPLETED, FAILED, TIMEOUT Si l'exécution est complète, aucune exception n'est jetée, et il n'y a pas de timeout de tâche, l'exécution est marquée comme COMPLETED et la tâche est marquée comme QUEUED ou COMPLETED selon si elle doit être exécutée à nouveau plus tard ou non. La sortie de la tâche est sérialisée en JSON et stockée dans l'enregistrement de l'exécution. Lorsqu'une tâche RUNNING jette une exception, sont exécution est marquée comme FAILED et la tâche est marquée comme FAILED. La traceback est stockée dans l'enregistrement de l'exécution.

De même, lorsqu'une exécution excède le timeout, elle est stoppée et marquée comme TIMEOUT, et la tâche est marquée comme TIMEOUT.

Dans tous les cas, la sortie standard stdout est capturée et stockée également dans l'enregistrement de l'exécution.

En utilisant appadmin, on peut vérifier toutes les tâches RUNNING, la sortie des tâches COMPLETED, l'erreur des tâches FAILED, ...

L'ordonnanceur créé également une table supplémentaire appelée "scheduler_worker", qui stocke les heartbeat des workers et leurs statuts.

Gérer les processus

La gestion fine des workers est difficile. Ce module essaie de ne pas rester derrière chaque plateforme (Mac, Win, Linux).

Lorsque vous démarrez un worker, vous pourriez vouloir plus tard :

  • le tuer "quoi qu'il arrive"
  • le tuer seulement s'il n'exécute pas de tâche
  • le mettre en pause

Peut⁻être que vous avez déjà certaines tâches en attente, et que vous voulez sauver quelques ressources. Vous savez que vous voulez les exécuter toutes les heures, donc, vous voudrez :

  • exécuter toutes les tâches en attente et les laisser mourir automatiquement

Toutes ces choses sont possibles en gérant les paramètres Scheduler ou la table scheduler_worker. Pour être plus précis, pour les workers démarrés vous pouvez changer la valeur du status de n'importe quel worker pour influencer son comportement. Comme pour les tâches, les workers peuvent être dans l'un des statuts suivants : ACTIVE, DISABLED, TERMINATE ou KILLED.

ACTIVE et DISABLED sont "persistent", tant que TERMINATE ou KILL, comme leurs noms de statut le suggèrent, sont plus des commandes que des statuts réels. Utiliser ctrl+c revient à définir un worker à KILL

workers statuses

Il y a quelques fonctions de commodités depuis la version 2.4.1 (qui se comprennent par elles-mêmes)

scheduler.disable()
scheduler.resume()
scheduler.terminate()
scheduler.kill()

chaque fonction prend un paramètre optionnel, qui peut être une chaîne ou une liste pour gérer les workers basés sur leur group_names. Il utilise par défaut le group_names défini dans l'instanciation du scheduler.

Un exemple est bien meilleur qu'un millier de mots : scheduler.terminate('high_prio') va TERMINATE tous les workers qui exécutent les tâches high_prio, là où scheduler.terminate(['high_prio', 'low_prio']) va terminer tous les workers high_prio et low_prio.

Attention : si vous avez un worker exécutant high_prio et low_prio, scheduler.terminate('high_prio') terminera tous les workers ensemble, même si vous ne souhaitiez pas terminer les low_prio.

Tout ce qu'il est possible de faire via appadmin peut être fait en programmation en insérant/mettant à jour les enregistrement dans ces tables.

Peu importe, les enregistrements relatifs aux tâches RUNNING ne devraient pas être mises à jour puisque cela peut engendrer un comportement non voulu. La bonne pratique est de mettre en attente des tâches en utilisant la méthode "queue_task".

Par exemple :

scheduler.queue_task(
    function_name='task_add',
    pargs=[],
    pvars={'a':3,'b':4},
    repeats = 10, # run 10 times
    period = 3600, # every 1h
    timeout = 120, # should take less than 120 seconds
    )

Notez que les champs "times_run", "last_run_time" et "assigned_worker_name" ne sont pas fournis à l'heure prévue mais sont remplis automatiquement par les workers.

Vous pouvez également récupérer la sortie des tâches complétées :

completed_runs = db(db.scheduler_run.run_status='COMPLETED').select()

L'ordonnanceur est considéré comme expérimental car il nécessite des tests approfondis et car la structure de table peut changer à tout moment pour l'ajout de nouvelles fonctionnalités.

Pourcentage de progression des rapports

Un "mot" spécial rencontré dans les états d'impression de vos fonctions effacent tous les précédentes sorties. Ce mot est !clear!. Celui-ci couplé au paramètre sync_output, autorise des pourcentages de rapport.

Voici un exemple :

def reporting_percentages():
    time.sleep(5)
    print '50%'
    time.sleep(5)
    print '!clear!100%'
    return 1

La fonction reporting_percentages dort pendant 5 secondes, renvoie 50%. Ensuite, elle dort à nouveau 5 secondes et renvoie 100%. Notez que la sortie dans une table scheduler_run est synchronisée toutes les 2 secondes que ce second état d'affichage qui contient !clear!100% obtient l'effacement de la sortie 50% et le remplace par 100% seulement.

scheduler.queue_task(reporting_percentages,
                     sync_output=2)

Modules tiers

import

web2py est écrit en Python, donc il peut importer et utiliser tout module Python, incluant des modules tiers. Il nécessite uniquement d'être capable de les trouver. Comme avec n'importe quelle application Python, les modules peuvent être installés dans le répertoire officiel "sites-packages", et il peuvent être importés depuis n'importe où dans votre code.

Les modules dans le répertoire "sites-packages" sont, comme le nom l'indique, des packages au niveau site. Les applications nécessitant site-packages ne sont pas portables à moins que ces modules soient installés séparément. L'avantage d'avoir des modules dans "site-packages" est que de multiples applications peuvent les partager. Considérons, par exemple, le package d'affichage de courbes appelé "matplotlib". Vous pouvez l'installer depuis le shell en utilisant la commande PEAK easy_install [easy-install] (ou son remplacement moderne pip [PIP] ) :

easy_install py-matplotlib

et vous pouvez ensuite l'importer dans n'importe quel modèle/contrôleur/vue avec :

import matplotlib

La distribution des sources web2py, et la disctribution binaire Windows ont un site-packages dans le répertoire de haut-niveau. La distribution binaire Mac a un répertoire site-packages dans le répertoire :

web2py.app/Contents/Resources/site-packages

Le problème avec l'utilisation de site-packages est qu'il devient difficile d'utiliser différentes version d'un simple module au même moment, par exemple il pourrait y avoir deux applications mais que chacune d'entre elles utilise une version différente du même fichier. Dans cet exemple, sys.path ne peut pas être altéré car cela affecterait les deux applications.

Pour ce genre de situation, web2py fournit un autre moyen d'importer des modules de sorte que le sys.path global ne soit pas altéré : en les plaçant dans dossier "modules" d'une application. Un autre bénéfice est que le module sera automatiquement copié et distribué avec l'application.

Une fois qu'un module "mymodule.py" est placé dans un répertoire "modules/" de l'application, il peut être importé depuis n'importe où à l'intérieur d'une application web2py (sans avoir besoin de modifier sys.path avec) :

import mymodule

Environnement d'exécution

exec_environment

Alors que tout ce qui est décrit ici fonctionne bien, nous recommandons plutôt de construire votre application en utilisant les composant, comme expliqué dans le chapitre 12.

Les fichiers de modèle et de contrôleur web2py ne sont pas des modules Python et ne peuvent pas être importés en utilisant la déclaration Python import. La raison de cela est que les modèles et les contrôleurs sont voués à être exécutés dans un environnement préparé qui a été pré-rempli avec des objets globaux web2py (request, response, session, cache et T) et des fonctions helper. Ceci est nécessaire puisque Python est un langage considéré statique, alors que l'environnement web2py est créé dynamiquement.

web2py fournit la fonction exec_environment pour vous permettre d'accéder aux modèles et aux contrôleurs directement. exec_environment créé un environnemnet d'exécution web2py, charge le fichier et retourne ensuite un objet Storage contenant l'environnemnet. L'objet Storage sert également de mécanisme d'espace de nom. Tout fichier Python désigné à être exécuté dans un environnement d'exécution peut être chargé en utilisant exec_environment. Les utilisation pour exec_environment incluent :

  • Accéder aux données (modèles) depuis les autres applications.
  • Accéder aux objets globaux depuis les autres modèles ou contrôleurs.
  • Exécuter les fonctions de contrôleur depuis les autres contrôleurs.
  • Charger les librairies de helper étendues.

Cet exemple lit les lignes depuis la table user dans l'application cas :

from gluon.shell import exec_environment
cas = exec_environment('applications/cas/models/db.py')
rows = cas.db().select(cas.db.user.ALL)

Un autr exemple : supposez que vous ayez un contrôleur "other.py" qui contient :

def some_action():
    return dict(remote_addr=request.env.remote_addr)

Voici comment vous pouvez appeler cette action depuis un autre contrôleur (ou depuis le shell web2py) :

from gluon.shell import exec_environment
other = exec_environment('applications/app/controllers/other.py', request=request)
result = other.some_action()

En ligne 2, request=request est optionnel. Il a pour effet de passer la requête courante à l'environnement "other". Sans cet argument, l'environnement contiendrait un nouvel objet vide de requête (sauf pour request.folder). Il est également possible de passer une réponse un objet de session à exec_environment. Attention lorsque vous passez des objets request, response et session --- une modification depuis l'action appelée ou des dépendances de code dans l'action appelée pourraient entraîner des effets de bord non souhaités.

L'appel de fonction à la ligne 3 n'exécute pas la vue ; il retourne simplement le dictionnaire à moins que response.render soit appelé explicitement par "some_action".

Un dernier avertissement : n'utilisez pas exec_environement de manière inappropriée. Si vous voulez les résultats d'une action dans une autre application, vous devrez probablement implémenter une API XML-RPC (implémentation quasi triviale avec web2py). N'utilisez pas exec_environment en tant que mécanisme de redirection ; utilisez l'helper redirect.

Coopération

cooperation

Il y a de nombreux moyens de faire coopérer des applications :

  • Les application peuvent se connecter à la même base et donc partager des tables. Il n'est pas nécessaire que toutes les tables dans la base soient définies par toutes les applications, mais elles peuvent être définies par les application qui les utilisent. Toutes les applications qui utilisent la même table, sauf une, doivent définir la table avec migrate=False.
  • Les application peuvent embarquer des composants depuis d'autres applications en utilisant le helper LOAD (décrit dans le chapitre 12).
  • Les applications peuvent partager des sessions.
  • Les applications peuvent s'appeler à distance à travers XML-RPC.
  • Les applications peuvenet accéder aux fichiers des autres via le système de fichiers (en supposant qu'ils partagent le même système de fichiers).
  • Les application peuvent appeler d'autres actions localement en utilisant exec_environment comme présenté juste au-dessus.
  • Les applications peuvent importer des modules d'une autre application en utilisant la syntaxe :
from applications.otherapp.modules import mymodule

ou

import applications.otherapp.modules.othermodule
  • Les application peuvent importer n'importe quel module dans le chemin de recherche PYTHONPATH, sys.path.

Une application peut charger la session d'une autre application en utilisant la commande :

session.connect(request, response, masterapp='appname', db=db)

Ici, "appname" est le nom de l'application principale, cette qui définir le session_id initial dans le cookie. db est une connexion à la base de données qui contient la table de session (web2py_session). Toutes les applications qui partagent les sessions doivent utiliser la même base de données pour le stockage de session.

Logging

Python fournit des APIs de loggins. Web2py fournit un mécanisme pour le configurer afin que les applications puissent l'utiliser.

Dans votre application, vous pouvez créer un logger, par exemple dans un modèle :

import logging
logger = logging.getLogger("web2py.app.myapp")
logger.setLevel(logging.DEBUG)

et vous pouvez l'utiliser pour logger les messages de différentes importances

logger.debug("Just checking that %s" % details)
logger.info("You ought to know that %s" % details)
logger.warn("Mind that %s" % details)
logger.error("Oops, something bad happened %s" % details)

logging est un module standard python décrit ici :

http://docs.python.org/library/logging.html

La chaîne "web2py.app.myapp" définit un logger de niveau application.

Pour que ceci fonctione correctement, vous avez besoin d'un fichier de configuration pour le logger. L'un est fourni par web2py dans le dossier "examples" : "logging.example.conf". Vous aurez besoin de copier ce fichier vers le répertoire web2py et de le renommer en "logging.conf" puis de le personnaliser comme vous le souhaitez.

Ce fichier est auto-documenté, donc vous devriez l'ouvrir et le lire.

Pour créer un logger configurable pour l'application "myapp", vous devez ajouter myapp à la list des clés [loggers] :

[loggers]
keys=root,rocket,markdown,web2py,rewrite,app,welcome,myapp

et vous devez ajouter une section [logger_myapp], en utilisant [logger_welcome] comme point de départ.

[logger_myapp]
level=WARNING
qualname=web2py.app.myapp
handlers=consoleHandler
propagate=0

La directive "handlers" spécifie le type de logging et ici le log est effectué sur la console pour "myapp".

WSGI

WSGI

web2py et WSGI ont une relation amour-haine. Notre perspective est que WSGI a été développé comme un protocole pour connecter des serveurs web à des applications web de manière portable, et nous l'utilisons dans cette optique. web2py, en son coeur, est une application WSGI : gluon.main.wsgibase. Certains développeurs ont poussé WSGI à ses limites en tant que protocole pour des communications entre middleware et développement des applications web comme un oignon à plusieurs couches (chaque couche étant un middleware WSGI développé indépendamment du framework complet). Web2py n'adopte pas cette structure en interne. C'est parce que nous ressentons que la fonctionnalité principale des frameworks (gestion des cookies, session, erreurs, transactions, dispatching) peut être mieux optimisée pour la vitesse et la sécurité si elles sont gérées par une simple couche compréhensible.

Maintenant web2py permet d'utiliser des applications WSGI tierces et des middleware de trois façons (et leurs combinaisons) :

  • Vous pouvez éditer le fichier "wsgihandler.py" et inclure n'importe quel middleware WSGI tiers.
  • Vous pouvez connecter un middleware WSGI tiers à n'importe quelle action spécifique dans vos applications.
  • Vous pouvez appeler une application WSGI tierce depuis vos actions.

La seule limitation est que vous ne pouvez pas utiliser de middleware tiers pour remplacer les fonction principales web2py.

Middleware externe

Considérez le fichier "wsgibase.py" :

#...
LOGGING = False
#...
if LOGGING:
    application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase,
                                        logfilename='httpserver.log',
                                        profilerfilename=None)
else:
    application = gluon.main.wsgibase

Lorsque LOGGING est défini à True, gluon.main.wsgibase est packagé par la fonction middleware gluon.main.appfactory. Elle fournit les logs dans le fichier "httpserver.log". Sur le même principe, vous pouvez ajouter n'importe quel middleware tiers. Nous vous référons vers la documentation officielle WSGI pour de plus amples détails.

Middleware interne

Etant donnée n'importe quelle action dans vos contrôleurs (par exemple index) et n'importe quelle application middleware tierce (par exemple MyMiddleware, qui convertit la sortie en majuscule), vous pouvez utiliser un décorateur web2py pour appliquer le middleware à cette action. Voici un exemple :

class MyMiddleware:
    """converts output to upper case"""
    def __init__(self,app):
        self.app = app
    def __call__(self, environ, start_response):
        items = self.app(environ, start_response)
        return [item.upper() for item in items]

@request.wsgi.middleware(MyMiddleware)
def index():
    return 'hello world'

Nous ne pouvons pas promettre que tous les midlewares tiers fonctionneront avec ce mécanisme.

Appeler des applications WSGI

Il est facile d'appeler une application WSGI depuis une action web2py. Voici un exemple :

def test_wsgi_app(environ, start_response):
    """this is a test WSGI app"""
    status = '200 OK'
    response_headers = [('Content-type','text/plain'),
                        ('Content-Length','13')]
    start_response(status, response_headers)
    return ['hello world!\n']

def index():
    """a test action that calls the previous app and escapes output"""
    items = test_wsgi_app(request.wsgi.environ,
                          request.wsgi.start_response)
    for item in items:
        response.write(item,escape=False)
    return response.body.getvalue()

Dans ce cas, l'action index appelle test_wsgi_app et échappe la valeur retournée avant de la renvoyer. Notez que index n'est pas lui-même une application WSGI et doit utiliser l'API basique web2py (telle que response.write pour écrire dans la socket).

 top