Je prévois un logiciel de discussion utilisant Python avec Twisted, Storm et Jinja. Le problème est que Jinja n'a pas été conçu pour les bibliothèques de sockets Twisted ou asynchrones, et les performances fournies par l'utilisation de Twisted sont la raison pour laquelle je ne prévois pas d'utiliser Flask.

Alors, comment puis-je avoir des pages Web de rendu Twisted en utilisant Jinja?

3
TeamBlast 5 nov. 2011 à 00:19

3 réponses

Meilleure réponse

Vous pouvez rendre des pages Web en utilisant Jinja de la même manière que vous utiliseriez n'importe quelle autre bibliothèque Python dans Twisted. Vous venez de l'appeler. Cela fonctionnera bien avec Twisted, bien que vous puissiez rencontrer des problèmes de performances si Jinja fait quelque chose de bloquant. Notez qu'il est possible d'utiliser très bien les bibliothèques de blocage avec Twisted, soit via deferToThread, soit simplement en bloquant la boucle principale si ce n'est pas un problème de performances. Donc, j'en déduis que votre question est vraiment de savoir comment utiliser Jinja sans bloquer.

Jinja est une bibliothèque de modèles, ce qui signifie qu'elle lit un modèle, appelle une logique d'affichage sur le modèle et écrit une sortie HTML. Il y a donc 3 choses qui peuvent bloquer:

  1. lire le modèle,
  2. écrire le résultat.
  3. exécuter la logique d'affichage (votre code d'application),

Je ne connais pas Jinja, donc je ne sais pas exactement comment les API pour chacune de ces choses sont structurées et je ne peux pas vous dire quoi faire, mais je suppose que cette partie est facile; donc, je vais vous donner une réponse générale sur les bibliothèques de modèles tiers et Twisted.

Je vais donc répondre à chacune de ces préoccupations, mais pas tout à fait dans l'ordre:

1. Lecture du modèle

Vraiment, la chose la plus raisonnable à faire ici est de ne pas s'en soucier . La lecture du modèle est probablement très rapide. Ce sont de petits fichiers fréquemment consultés, que votre système d'exploitation conserve presque certainement dans son cache de système de fichiers. Il est peu probable que vous ne bloquiez jamais leur lecture à moins que vous ne fassiez quelque chose de fou comme les mettre sur NFS. Si vous profilez votre application et constatez que c'est un problème - parce que, disons, vous avez des disques extrêmement lents ou un système de fichiers distant - lisez simplement le modèle dans un cStringIO ou quelque chose de similaire au démarrage et alimentez-le à jinja après ça.

3. Rédaction de la réponse

Les pages Web ne sont pas si grandes, et Twisted ne fournit pas d'API de blocage pour écrire sur les sockets. Au lieu de cela, il propose une API qui ne fait que mettre le résultat en mémoire tampon jusqu'à ce qu'il puisse être écrit. Ma suggestion est de faire essentiellement la même chose ici qu'avec la lecture du modèle: à moins que vous n'ayez une sortie vraiment énorme, il est probablement correct de brûler un peu de RAM pendant que la réponse est envoyée au client.

2. Exécution de View Logic

C'est le domaine où vous êtes le plus susceptible de rencontrer des problèmes. Jinja ne gère probablement pas les résultats des Deferred. Mais en fait, ce n'est pas réellement Jinja qui va vous poser directement des problèmes: c'est Storm. Storm s'attend à pouvoir bloquer, effectuer des requêtes de base de données lorsque vous accédez à certains attributs. Parler des blocs de base de données, et c'est la source la plus significative de blocage des E / S dans la plupart des applications Web. Vous devez donc décider comment vous allez y faire face. Vous avez quelques options:

  1. Faites-le simplement dans le fil principal et ne vous en faites pas. Peut-être que votre demande concerne un groupe de travail de 10 personnes et que votre base de données est locale. Bien sûr, vos E / S vont se bloquer, mais si elles répondent toujours à leurs exigences de performances, qui s'en soucie? Toutes les applications ne doivent pas s'adapter à la lune et à l'arrière.
  2. Préléchargez tout de Storm dans un appel deferToThread (ou similaire) et assurez-vous que Jinja n'accède qu'aux objets en mémoire. De cette façon, vous pouvez exécuter vos moteurs de rendu dans votre thread principal, dans un rappel sur un Deferred qui faisait des E / S de base de données. Si vous n'accédez qu'à des objets en mémoire, votre rendu peut encore prendre un peu de temps, mais ça va. Cette question m'a incité à publier un article sur mon blog sur la distinction entre "bloquer" et "exécuter" qui traînait en tant que brouillon depuis un bon moment; vous voudrez peut-être aller le lire.
  3. Faites tout votre rendu dans un thread ou un sous-processus et traitez-le comme un composant bloquant de votre programme. Cela perd certains des avantages de l'utilisation de Twisted, mais c'est toujours une stratégie parfaitement viable pour intégrer un composant bloquant Jinja / Storm et un composant non bloquant pur-Twisted, la partie réelle de relais de message de chat.

Si aucune de ces options ne vous convient, Twisted a inclus une bibliothèque de modèles que fait supporte les Deferred s depuis la version 11.0. Vous pouvez envisager d'utiliser twisted.web.template comme alternative à Jinja.

15
Glyph 5 nov. 2011 à 01:41

Je pense que le système de modèles Tornado (c'est comme le modèle Jinja2 car c'est un Django-like ...) peut être utilisé sans la tornade elle-même:

Nous avons essayé de nettoyer la base de code pour réduire les interdépendances entre les modules, vous devriez donc (théoriquement) pouvoir utiliser n'importe lequel des modules indépendamment dans votre projet sans utiliser le package complet.

Modèles de tornade

0
Abdelouahab 20 nov. 2011 à 11:24

Voici un exemple d'exemple sur la façon d'implémenter la solution 3, avec la prise en charge de la fonction de retour différé de base:

from jinja2 import Template
from twisted.internet import threads, reactor, defer

def inThread(f):
    def new_f(*args, **kwargs):
        return threads.deferToThread(f, *args, **kwargs)
    return new_f


def fromThread(f):
    def new_f(*args, **kwargs):
        return threads.blockingCallFromThread(reactor, lambda: defer.maybeDeferred(f, *args, **kwargs))
    return new_f


class DeferredTemplate(Template):
    def render(self, **kw):
        hooked_kw = {}
        for k, v in kw.iteritems():
            # decorate the callable so that they are run in the main thread
            if callable(v):
                v = fromThread(v)
            hooked_kw[k] = v
        return inThread(Template.render)(self, **hooked_kw)

from twisted.trial import unittest
class TestJinjaDeferred(unittest.TestCase):
    @defer.inlineCallbacks
    def test_basic(self):
        def getHello():
            d = defer.Deferred()
            reactor.callLater(0.0, lambda: d.callback("Hello"))
            return d

        def getWorldSync():
            return "world"

        template = DeferredTemplate("{{ getHello() }} {{ getWorldSync() }}")
        res = yield template.render(getHello=getHello, getWorldSync=getWorldSync)
        self.assertEqual(u"Hello world", res)
2
tardyp 10 févr. 2014 à 18:52
8015272