2017-03-15 1 views
2

Je travaille actuellement sur une petite application en Python (3.6) capable de gérer des portefeuilles de titres, et d'en faire une analyse financière.Alternative à SQLAlchemy ORM reconstructor

Les fonctionnalités sont simplement une interface CRUD (Créer, Lire, Mettre à jour, Supprimer) pour mes portefeuilles et actions. Ils peuvent être conservées dans un dabase (en utilisant PostgreSQL et SQLAlchemy comme ORM)

La chose est, je visais d'avoir un découplage absolu entre:

  • La « logique métier » (situé dans un package core), où je mes objets python Stock, Portfolio, Regression, etc.
  • la « couche de persistance », (situé dans un paquet séparé database), où toutes les métadonnées de SQLAlchemy et la mise en correspondance avec les objets de l'emballage core sont def INED

Je veux cette séparation pour plusieurs raisons: d'abord, je veux être en mesure de lancer le paquet core en mode autonome (avec ni aucune DB installé ni même SQLAlchemy) et, d'autre part, je pense qu'il est une bonne pratique, parce que dans mon paquet core, je peux vraiment se concentrer sur l'analyse et ne « polluer » mes objets Python avec le SQLA Column, relationships, etc.

j'ai réussi à le faire jusqu'à présent, en utilisant le SQLAlchemy bas niveau mapper et Table. Cependant, il y a un seul point que je ne peux pas comprendre. Actuellement, mon objet Python Stock est comme:

class Stock: 

    def __init__(self, ticker: str, exchange: Exchange, name: str=None): 
     self.ticker = ticker 
     self.exchange = exchange 
     self.name = name 
     self._data = None 

    ... 

La propriété _data est nécessaire pour stocker mes données financières, et je veux instancier.

Mais, comme vous le savez, quand je charge une Stock de la base de données, (dans le cas où je l'utilise en fait le database pkg), par exemple via une commande comme session.query(Stock).first(), la fonction __init__ est pas appelée par SQLA . La seule chose que je peux faire est d'ajouter une fonction:

@orm.reconstructor 
def init_on_load(self): 
    self._data = None 

orm est sqlalchemy.orm et est le seul importation de sqlalchemy dans mon core pkg. Je voudrais m'en débarrasser!

Est-ce que quelqu'un a une idée? Par exemple, il existe peut-être un moyen d'instancier la propriété _data à l'intérieur du package database, via le mapper, mais je n'en sais pas.

Merci

+0

Si tout ce dont vous avez besoin est une valeur par défaut ('None' dans ce cas), ne pouvez-vous pas simplement mettre' _data = None' au niveau de la classe? – univerio

+1

"[' reconstructor() '] (http://docs.sqlalchemy.org/en/latest/orm/constructors.html#sqlalchemy.orm.reconstructor) est un raccourci vers un plus grand système d'événements de" niveau instance ", qui peut être abonné à l'aide de l'API d'événement - voir [InstanceEvents] (http://docs.sqlalchemy.org/en/latest/orm/events.html#sqlalchemy.orm.events.InstanceEvents) pour la description complète de l'API de ces events. ", dont [' load'] (http://docs.sqlalchemy.org/en/latest/orm/events.html#sqlalchemy.orm.events.InstanceEvents.load) pourrait vous intéresser. –

+0

Merci pour votre réponse. Je jetterai un coup d'oeil à la section de gestion d'événements, et particulièrement à cet événement de charge, et posterai une réponse avec ma solution, en vous citant. Ou si vous préférez, vous pouvez rediffuser votre commentaire en réponse, afin que je puisse le valider! – Edouardb

Répondre

-1

EDIT:

Réponse au commentaire, « datamapper n'instrusive donc vous pouvez utiliser une classe de données sur votre couche de données et la couche d'affaires ». Ce n'est vrai que si le projet est petit. mais lorsque les projets prennent de l'ampleur, le magasin de données sous-jacent ne change pas au même rythme que votre logique métier. Ensuite, vous avez besoin de couches intermédiaires, par exemple des adaptateurs, des transformateurs, etc.Quoi qu'il en soit, si votre problème ne concerne pas le découplage, il suffit de le faire fonctionner, puis utilisez le @ orm.reconstructor ou n'importe quel hacks. Sinon, utilisez le moins de hacks ou la solution de contournement possible.

======================================

Sur la base de mon expérience , Les classes ORM sont couplées avec le framework OMR sous-jacent, quel que soit le framework choisi, SQLAlchemy, MongoEngine, etc.

Si vous souhaitez extraire le stockage de données et pouvoir changer de moteur de données à l'avenir. Ensuite, mieux écrire votre logique d'entreprise de base sur certaines classes ordinaires. Ensuite, dans le module de stockage, fournissez une fonction pour convertir la classe ORM en classe simple.

par exemple

# module core.objects 
class Stock: 
    def __init(self, name, price): 
     self.name = name 
     self.price = price 

# business logic 
class BusinessLogic: 
    @staticmethod 
    def logic(stocks: list[Stock]): 
     pass 

Ensuite, dans le paquet de stockage

from core.objects import Stock as CoreStock 
# This is the ORM dependent class 
class Stock(Model): 
    name = columns.Text() 
    price = columns.Float() 

    @classmethod 
    def to_core_objects(instances): 
     return [CoreStock(instance.name, instance.price) for instance in instances] 

Maintenant, vous pouvez facilement changer de moteurs de stockage, mysql, csv etc, et beaucoup plus facile d'écrire des tests unitaires.

+0

"Les classes ORM sont couplées au cadre OMR sous-jacent" est plutôt vrai pour les ORM basés sur Active Record, mais pas nécessairement pour les mappeurs de données (OP a déjà séparé la logique dans les classes normales et la persistance en utilisant le mapping). –

+0

ORM est orm. C'est intrusif, le mappeur de données fonctionne seulement pour un cadre ORM, et il a son propre caprice. Sinon, l'OP n'a pas de tels problèmes. L'intention est une chose, la mise en œuvre en est une autre. @ IljaEverilä – Shuo

+0

[Aucun hacks requis] (http://docs.sqlalchemy.org/en/latest/orm/events.html#sqlalchemy.orm.events.InstanceEvents.load). Le décorateur 'reconstructor()' est juste une fonction de commodité autour de l'événement d'instance de chargement, qui permet le découplage désiré. –