Journal Après le sucre, le JSON !

Posté par . Licence CC by-sa
Tags : aucun
11
4
fév.
2013

Cher Nal'

Je sais pas si tu te souviens, mais la semaine dernière j'avais chuchoté dans le creux de ton oreille, que j'ai écrit une petite bibliothèque pour faire un peu de sucre syntaxique autour d'SQLAlchemy : Un peu de sucre pour une meilleure alchimie ?.

Un commentaire d'amélioration a retenu mon attention. Et j'ai, depuis, commité les changements pour que le sucre soit encore un peu plus sucré. Maintenant on peut faire :

   >>> MyObj.all()
   >>> MyObj.get(id=2)
   >>> MyObj.filter(name__like='toto')

Au lieu de :

   >>> MyObj.all(session)
   >>> MyObj.get(session, id=2)
   >>> MyObj.filter(session, status__name='toto')

C'est quand même un peu plus cool non ?

Et en plus de ça, j'ai mis la doc sur RTD.

Mais je ne me suis pas arrêté là puis que j'ai ajouté quelques petites fonctionnalités qui permettent de charger ou de dumper un objet depuis/vers un dictionnaire python. C'est notamment fait pour faciliter les échanges de données au format JSON. Ce qui peut être pratique quand on fait un peu de web.

   >>> obj = MyObj.get(id=1)
   >>> import json
   >>> json.dumps(obj.dump(), indent=4)
   {
        "status": {
            "id": 1,
            "name": "Ok"
        },
        "status_id": 1,
        "id": 1,
        "name": "Great Obj"
   }
   >>> obj = MyObj.load({'id': 1, 'name': 'Bigger Obj'})
   >>> json.dumps(obj.dump(), indent=4)
   {
        "status": {
            "id": 1,
            "name": "Ok"
        },
        "status_id": 1,
        "id": 1,
        "name": "Bigger Obj"
   }
   >>> session.commit()
   >>> obj = MyObj.get(id=1)
   >>> obj.id, obj.name
   (1, "Bigger Obj")

Du coup, en imaginant que le client HTTP envoit une rêquete POST vers /obj/1/save/

Avec un POST qui ressemblerait à ça :

{
  'obj': {
     'id': 1
     'name': 'toto'
     'status': {
        ....
     }
  }
}

On peut se contenter de faire du côté du serveur:

def save_obj(request):
    obj = MyObj.load(request.POST['obj'])
    session.add(obj)
    session.commit()
    return HTTPOk(body=obj.dump(), content_type='application/JSON')

La méthode load s'occupe de récupérer en base les objets et les sous-objets si les attributs qui composent la clef primaire sont présents (id dans l'exemple). Sinon, elle créée une nouvelle instance d'objet.

Ainsi plus besoin de programmer manuellement le mis-à-jour où l'ajout des objets en relation, on peut simplement passé un dictionnaire qui représente la structure des objets.

Encore une fois cher journal, si tu connais des bibliothèques qui font ça mieux que moi, je suis preneur !

  • # Petit detail

    Posté par . Évalué à 3.

    Qui n'en est pas vraiment un: minifies le JSON par defaut et utilise un plugin safari/chrome pour l'afficher correctement.
    1) ton serveur sera 'achement content d'avoir moins de charge de bande passante
    2) tes clients aussi (et un parsing probablement un pouillieme plus rapide)
    3) tu pourras faire des trucs fancy dans ton browser avec le json, comme plier des parties de l'arbre etc.

    Sinon, question bête: comment tu fais si t'as des fields que tu veux pas serializer? Typiquement, information confidentielles qui vivent cote serveur et ne doivent pas etre envoye au client, genre numero de secu, nom/prenom ou autre?

    Linuxfr, le portail francais du logiciel libre et du neo nazisme.

    • [^] # Re: Petit detail

      Posté par . Évalué à 1.

      Sinon, question bête: comment tu fais si t'as des fields que tu veux pas serializer?

      Il y a un paramètre "excludes" que tu peux passer à dump. Tu y listes les champs que tu veux exclure

      user.dump(excludes=['fullname', 'bank_account_number'])

      • [^] # Re: Petit detail

        Posté par . Évalué à 2.

        Du coup il faut y penser à chaque deserialisation ?

        Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

        • [^] # Re: Petit detail

          Posté par . Évalué à 1.

          Du coup il faut y penser à chaque deserialisation ?

          Pour le moment oui. J'avoue ne pas en avoir eu trop le besoin , mais si il y a une demande, je me pencherai sur la question.

    • [^] # Re: Petit detail

      Posté par (page perso) . Évalué à 0.

      JCVD, sors de ce corps !

  • # Session

    Posté par . Évalué à 3.

    Salut,

    Pour la première fonctionnalité, comment la session est-elle récupérée ?
    Est-il du coup possible d'injecter sa propre session pour tester une méthode par exemple ?

    Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: Session

      Posté par . Évalué à 1.

      Est-il du coup possible d'injecter sa propre session pour tester une méthode par exemple ?

      Absolument.

      Il y a une méthode "register_sessionmaker" qui prend une fonction en paramètre. On attend de cette fonction qu'elle renvoie une session.

      BaseModel.register_sessionmaker(scoped_session(sessionmaker(bind=engine)))
      
      

      ou bien pour une session globale

      BaseModel.register_sessionmaker(lambda: DBSession)
      
      

      C'est dit ici si tu veux plus d'info.

      C'est encore en dev, si tu as des remarques je serai content de les recevoir :)

    • [^] # Re: Session

      Posté par . Évalué à 2.

      J'allais poser la même question, donc je rebondis sur ce commentaire pour en poser d'autres :
      * les anciennes méthodes, où on pouvait passer 'session' existent-elles toujours ?
      * Si oui, ne pas passer ce paramètre signifie-t-il prendre la dernière session utilisée/activée, et en passer une autre impose de se baser sur celle-ci ?

      • [^] # Re: Session

        Posté par . Évalué à 1.

        les anciennes méthodes, où on pouvait passer 'session' existent-elles toujours ?

        J'avoue ne pas avoir pris en compte que quelqu'un se servirait de la bibliothèque… Du coup, ce n'est pas rétro-compatible.

        Mais si tu l'utilises et que tu souhaites conserver la session en paramètre, je pourrais faire un patch qui ferait en sorte que:
        - si la session est passée en paramètre on l'utilise
        - sinon on la fabrique avec le "sessionmaker" enregistré.

        • [^] # Re: Session

          Posté par . Évalué à 2.

          non, je ne l'utilise pas : ma question se voulait générale, dans un souci de rétro-compatibilité.

          • [^] # Re: Session

            Posté par . Évalué à 3.

            Malheureusement partager la même Session est à double tranchant :

            • tu gagnes en écriture
            • tu perds en fonctionnalité : adieu les Threads.
            • [^] # Re: Session

              Posté par . Évalué à 1.

              tu perds en fonctionnalité : adieu les Threads.

              Je suis pas encore extremement compétent avec SQLAlchemy, mais il me semble que si l'on décore le sessionmaker avec sqlalchem.orm.scoped_session on a quelque chose qui tient le route

              "the ScopedSession provides a quick and relatively simple (if one is familiar with thread-local storage) way of providing a single, global object in an application that is safe to be called upon from multiple threads."

              D'ailleurs je l'utilise dans une application threadée au boulot et ça marche très bien.

              Après, je suis pas encore très au point avec SQLAlchemy, il y a encore des choses qui m'échappent, je suis pas contre quelques explications :)

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.