38

sqlautocode - has issues with many-to-many relations

sqlsoup - doesn't support relations

elixir - it's note auto-generate

Is there something else I could try?

Jonas
  • 121,568
  • 97
  • 310
  • 388
mdob
  • 2,224
  • 3
  • 22
  • 25
  • 1
    What exactly are you trying to do? If I understand you, you want to create declartive references from database tables that haven't been defined by an sqlalchemy model? I'm not sure you can get to the declarative point, but you can reflect table properties (I believe this includes things like [foreign keys](http://www.sqlalchemy.org/docs/05/metadata.html#reflecting-tables)) – Raceyman Jun 09 '11 at 13:04
  • 1
    As pointed out in [this answer](http://stackoverflow.com/a/14403228/3079302), SQLAlchemy has the [Automap extension](http://docs.sqlalchemy.org/en/latest/orm/extensions/automap.html?highlight=automap#module-sqlalchemy.ext.automap) since version 0.9.1. From the docs: Define an extension to the `sqlalchemy.ext.declarative` system which automatically generates mapped classes and relationships from a database schema, typically though not necessarily one which is reflected. – iled Jan 20 '16 at 16:36

3 Answers3

70

In theory reflection in sqlalchemy should work for you. In this case I'm using an mssql database with two tables which have a simple Many-to-one relation:

"Tests" with fields:

  • id
  • testname
  • author_id (foreign key to the Users table, Users.id field)

"Users" with fields:

  • id
  • fullname

So the following should reflect the database:

from sqlalchemy import *
from sqlalchemy.orm import create_session
from sqlalchemy.schema import Table, MetaData
from sqlalchemy.ext.declarative import declarative_base

#Create and engine and get the metadata
Base = declarative_base()
engine = create_engine('put your database connect string here')
metadata = MetaData(bind=engine)

#Reflect each database table we need to use, using metadata
class Tests(Base):
    __table__ = Table('Tests', metadata, autoload=True)

class Users(Base):
    __table__ = Table('Users', metadata, autoload=True)

#Create a session to use the tables    
session = create_session(bind=engine)

#Here I will just query some data using my foreign key relation,  as you would
#normally do if you had created a declarative data mode.
#Note that not all test records have an author so I need to accomodate for Null records
testlist = session.query(Tests).all()    

for test in testlist:
    testauthor = session.query(Users).filter_by(id=test.author_id).first()  
    if not testauthor:
        print "Test Name: {}, No author recorded".format(test.testname)
    else:
        print "Test Name: {}, Test Author: {}".format(test.testname, testauthor.fullname)

So this appears to work with table relations. Although you still haven't given much detail to exactly what you are trying to do.

Demitri
  • 13,134
  • 4
  • 40
  • 41
Raceyman
  • 1,354
  • 1
  • 9
  • 12
  • 2
    This example does not show the use of a relation between the tables at the object level, right? in other words, the fact that author_id is is a foreign key is not sufficient for SQLAlchemy to automatically have `test.fullname` be the correct `testauthor.fullname`, no? – Eric O. Lebigot Jan 19 '13 at 14:21
  • Are there libraries for doing this type of reflection in other programming languages besides python + SQLAlchemy?? This awesome! – maxm Apr 03 '14 at 17:50
  • 1
    Defining MetaData is not necessary. Creating Base also create MetaData instance that you can access via `Base.metadata`. – rgtk Jun 30 '15 at 21:18
29

Well I went through that, tried on Northwind database and it looks promising. Although, I had to add relationship field to be able to follow database relations.

Let's consider that I don't know relations between tables at the moment of starting the application so I need is a way to generate automatically.

import unittest

from sqlalchemy import *
from sqlalchemy.orm import create_session
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime
from sqlalchemy.orm import contains_eager, joinedload
from sqlalchemy.orm import relationship

#Create and engine and get the metadata
Base = declarative_base()
engine = create_engine('mssql://user:pass@Northwind', echo=True)
metadata = MetaData(bind=engine)


#Reflect each database table we need to use, using metadata
class Customer(Base):
    __table__ = Table('Customers', metadata, autoload=True)
    orders = relationship("Order", backref="customer")

class Shipper(Base):
    __table__ = Table('Shippers', metadata, autoload=True)
    orders = relationship("Order", backref="shipper")

class Employee(Base):
    __table__ = Table('Employees', metadata, autoload=True)
#    orders = relationship("Order", backref="employee")
    territories = relationship('Territory', secondary=Table('Employeeterritories', metadata, autoload=True))

class Territory(Base):
    __table__ = Table('Territories', metadata, autoload=True)
    region = relationship('Region', backref='territories')

class Region(Base):
    __table__ = Table('Region', metadata, autoload=True)


class Order(Base):
    __table__ = Table('Orders', metadata, autoload=True)
    products = relationship('Product', secondary=Table('Order Details', metadata, autoload=True))
    employee = relationship('Employee', backref='orders')

class Product(Base):
    __table__ = Table('Products', metadata, autoload=True)
    supplier = relationship('Supplier', backref='products')
    category = relationship('Category', backref='products') 

class Supplier(Base):
    __table__ = Table('Suppliers', metadata, autoload=True)

class Category(Base):
    __table__ = Table('Categories', metadata, autoload=True)


class Test(unittest.TestCase):

    def setUp(self):
        #Create a session to use the tables    
        self.session = create_session(bind=engine)        

    def tearDown(self):
        self.session.close()

    def test_withJoins(self):
        q = self.session.query(Customer)
        q = q.join(Order)
        q = q.join(Shipper)
        q = q.filter(Customer.CustomerID =='ALFKI')
        q = q.filter(Order.OrderID=='10643')
        q = q.filter(Shipper.ShipperID=='1')
        q = q.options(contains_eager(Customer.orders, Order.shipper))
        res = q.all()
        cus = res[0]
        ord = cus.orders[0]
        shi = ord.shipper
        self.assertEqual(shi.Phone, '(503) 555-9831')
mdob
  • 2,224
  • 3
  • 22
  • 25
26

You could use sqlacodegen to generate all the models from the database. However you need to take care of the foreign key manually.

Indradhanush Gupta
  • 4,067
  • 10
  • 44
  • 60