I am trying (purely as an exercice) to build a graphical software able to draw some charts representing Market data and doing some simple analysis.
I am using Python3.5 + SQLAlchemy1.1 for persistence (and PyQt but not relevant here).
To make it simple, I want to have a class 'Stock' representing a trading stock and a class 'Exchange' representing a market exchange (Paris market, London market, etc.). Each Stock has a Market.
By using Declarative Mapping, this would give the following:
from sqlalchemy import Column, ForeignKey, String
from sqlalchemy.orm import relationship
Base = declarative_base()
class Exchange(Base):
__tablename__ = 'exchange'
ticker = Column(String(2), primary_key=True)
name = Column(String(20))
class Stock(Base):
__tablename__ = 'stock'
ticker = Column(String(5), primary_key=True)
exchange_ticker = Column('exchange', String(2), ForeignKey('exchange.ticker'),
primary_key=True)
name = Column(String(20), unique=True)
exchange = relationship(Exchange)
But as I said, this is purely an exercice, and I would like to make it more 'decoupled': I would like to totally separate the 'business logic' from the 'persistence layer' (I am using quotes because I see these expressions everywhere and I think I got their meaning, but I am not a professional developer so I may not see the whole thing).
Ultimately, I would like to separate the business logic and the persistence layer in 2 modules (or packages, same), potentially 'gitsubmoduling' them, and be able to install and launch the logic module without any persistence (without even installing sqlalchemy / psycopg2 for PostgreSQL, etc.). This is a good practice, or at least a good objective, right?
So I had a deeper look at SQLAlchemy doc and I found the concept of Classical Mapping (automatically handled by Declarative Mapping). This would gives:
core.py (purely logic, possibly functional without any persistence)
class Exchange:
def __init__(self, ticker: str, name: str):
self.ticker = ticker
self.name = name
class Stock:
def __init__(self, ticker: str, exchange: Exchange, name: str=None):
self.ticker = ticker
self.exchange = exchange
self.name = name
# Some business logic: correlations, data analysis, etc.
model.py (mapping logic to database)
from sqlalchemy import Column, ForeignKey, Integer, MetaData, String, Table, UniqueConstraint
from sqlalchemy.orm import mapper
from core import Exchange, Stock
metadata = MetaData()
stock = Table(
'stock', metadata,
Column('ticker', String(5), primary_key=True),
Column('exchange', String(2), ForeignKey('exchange.ticker'), primary_key=False),
Column('name', String(20))
)
exchange = Table(
'exchange', metadata,
Column('ticker', String(2), primary_key=True),
Column('name', String(20))
)
mapper(Stock, stock)
mapper(Exchange, exchange)
This does not work: when I try to insert some fixtures:
sqlalchemy.exc.InterfaceError: (sqlite3.InterfaceError) Error binding parameter 2 - probably unsupported type. [SQL: 'INSERT INTO stock (ticker, name, exchange) VALUES (?, ?, ?)'] [parameters: ('CAC', None, <core.Exchange object at 0x108bd0fd0>)]
As you can see, he tries to 'insert' an Exchange object directly. Therefore, I tried (with different options and configs):
mapper(Stock, stock, properties={
'exchange': relationship(Exchange)
})
But SQLAlchemy raises an error:
sqlalchemy.exc.ArgumentError: WARNING: when configuring property 'exchange' on Mapper|Stock|stock, column 'exchange' conflicts with property '<RelationshipProperty at 0x1096d5cf8; exchange>'.
It seems that whatever I try (I tried a lot of other stuffs, with exclusive_properties
, naming the relationship
, etc.) SQLAlchemy wants a relationship object in my Stock (which makes sense..). So I am wondering is that level of decoupling is really reachable.
My questions are:
- Am I missing something obvious?
- Am I wrong to want that level of 'decoupling'?
- Am I facing the Object-relational impedance mismatch which is referenced in some places (as here, here or here)?
Aucun commentaire:
Enregistrer un commentaire