I want to use sqlalchemy declarative_base
to create all tables and relations for my database, however, I have separated my models across multiple files and now fail to find a suitable architecture to use it.
Calling declarative_base
returns a object which the models need to use as base class. However, it returns a new object every time it is called and the object needs to be the same for all models for it to work.
I have defined a small method for a database manager class which should construct the database. Naturally, for this to work the Base needs to be the same instance as used in all the models:
engine = sqlalchemy.create_engine("sqlite:///%s" % file)
create_database(engine.url) #sqlalchemy-utils
Base.metadata.create_all(engine) # declarative_base
The problem arises at all my solutions either violating PEP F401 due to unused imports or causing circular dependencies. If I do not import the models without using them the class will not be defined using declarative_base
and it won't be added to the database. While importing the models without using them will violate F401.
I fail to see how to create a clean construct with a shared declarative_base
for all the models and the database manager without having unused imports. How can an instance of declarative_base
be shared across models and database manager in such a way all tables will be created without unused imports?
After thoroughly read through your question I finally understand what you meant. The answer to your question worked out to be a lot simpler than I thought. I updated the answer to actually answer what you wanted. However, I still keep the previous examples as I thought you will benefit from them later on when you want to have a single instance of database manager.
For your programming problem, whether it is a single instance of Base or a single instance of database manager sharing across multiple files, it will all come down to a singleton design.
All 3 examples consist of 3 files, main.py, db.py, and main2.py. Example 1 is what you need for a single instance of the declarative Base. Example 1 will create test1.sqlite and test2.sqlite. Where example 2 and 3 will create "test1.sql" and "test2.sql". The previous example 1 and 2 now become 2 and 3.
These examples were not built package style but you can adapt them to your project easily. Also, another solution is to import with __init__.py.
For information on __init__.py, you can check here https://www.reddit.com/r/Python/comments/1bbbwk/whats_your_opinion_on_what_to_include_in_init_py/ and read a few answers here What is __init__.py for? .
For the examples, just run main.py.
db.py:
# Don't mind the import, remix to your usage case.
from sqlalchemy.ext.declarative import declared_attr, declarative_base
from sqlalchemy import *
import sqlalchemy_utils
# Declare attribute go here
class Base(object):
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
__table_args__ = {'mysql_engine': 'InnoDB'}
id = Column(Integer, primary_key=True)
# The only single instance of Base from here that can be share
# across all files.
Base = declarative_base(cls=Base)
# Other Schema for the design go here
# The first table will take on the name "MyModel"
# All this class derived from the single instance of Base above.
class MyModel(Base):
name = Column(String(1000))
class MyModel2(Base):
title = Column(String(1000))
class MyModel3(Base):
lastname = Column(String(1000))
main2.py:
import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from db import *
import sqlalchemy_utils
import sqlalchemy
# Another Model from here that use the only single instance of Base from db.
class MyModel7(Base):
surname = sqlalchemy.Column(sqlalchemy.types.String(1000))
def createDB():
fileName = "test2.sqlite"
engine = sqlalchemy.create_engine("sqlite:///%s" % fileName)
sqlalchemy_utils.create_database(engine.url)
tables_obj = [ MyModel.__table__,
MyModel2.__table__,
MyModel3.__table__,
MyModel7.__table__]
Base.metadata.create_all(bind=engine, tables=tables_obj)
main.py:
import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from db import *
from main2 import *
import sqlalchemy_utils
import sqlalchemy
fileName = "test1.sqlite"
# Another Model from here that use the only single instance of Base from db.
class MyModel4(Base):
lastname = sqlalchemy.Column(sqlalchemy.types.String(1000))
# Create a db file with multiple models "test1.sqlite" with the single
# declarative Base from here.
engine = sqlalchemy.create_engine("sqlite:///%s" % fileName)
sqlalchemy_utils.create_database(engine.url)
# Creating tables
tables_obj = [ MyModel.__table__,
MyModel2.__table__,
MyModel3.__table__,
MyModel4.__table__]
Base.metadata.create_all(bind=engine, tables=tables_obj)
# Create a db file "test2.sqlite" with
# the single instance of dclarative Base from main2
createDB()
db.py:
import sqlalchemy
import sqlalchemy_utils
class db():
# These variable can be convert to protected, etc...
engine = None
fileName = None
Base = None
# Not __init__ because manual init
def init(self, fileName):
if self.fileName is not None:
raise Exception("Already initialize. Use setConfig() instead.")
self.fileName = fileName
self.Base = sqlalchemy.ext.declarative.declarative_base()
'''
The engine will also initialize on creation.
'''
self.initEngine()
# Initialize the engine
def initEngine(self):
self.engine = sqlalchemy.create_engine("sqlite:///%s" % self.fileName)
sqlalchemy_utils.create_database(self.engine.url) #sqlalchemy-utils
# I don't even know why I called base here
# I am just copying the question
self.Base.metadata.create_all(bind=self.engine) # declarative_base
# Set config. Note that more can be set here. This is just an example.
def setConfig(self, fileName):
self.fileName = fileName
'''
One the config is changed, it is probably preferable to reset the engine.
Should garbage collector be run here? Does this consume too much memory
so that it benificial to call the garbage collector?
'''
self.initEngine()
# These methods below are to demonstrate the singleton instance
def createTable(self):
conn = self.engine.connect()
conn.execute("create table if not exists x (a integer, b integer)")
conn.close()
def insertData(self):
conn = self.engine.connect()
conn.execute("insert into x (a, b) values (1, 1)")
conn.execute("insert into x (a, b) values (2, 2)")
conn.execute("insert into x (a, b) values (3, 3)")
conn.execute("insert into x (a, b) values (5, 5)")
conn.close()
def getData(self):
conn = self.engine.connect()
'''
Convert result into list otherwise can't work with result on a closed
connection
'''
result = [conn.execute('''
select * from x;
''').fetchall()]
conn.close()
return result
def getEngineUrl(self):
return self.engine.url
db = db()
main2.py
import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from db import db
database = db
# Testing insert
def justTesting():
print("Currently working with engine " + str(database.getEngineUrl()))
database.insertData()
# Testing changing file
def changeFile():
database.setConfig("test2.sql")
database.createTable()
main.py
import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from db import db
from main2 import *
# Creating the db object. It need to be initialize 1.
database = db
database.init("test1.sql")
# Create the database table from here
database.createTable()
# Insert data into the table with main2
# Printing engine url with main2.
justTesting()
# get the result
results = database.getData()
for result in results:
print(result)
# Change the database from main2.
changeFile()
print("Currently working with engine " + str(database.getEngineUrl()))
# Test the error on reinitialization
try:
database.init("123.sql")
except Exception as e:
print("\n" * 5)
print("Error working fine. Just printing the message.")
print(e)
db.py
import sqlalchemy
import sqlalchemy_utils
class db(object):
# These variable can be convert to protected, etc...
_instance = None
engine = None
fileName = None
Base = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = object.__new__(cls)
return cls._instance
# __init__ any later object creation have to be the __new__(class)
# or error will occur.
def __init__(self, fileName):
self.fileName = fileName
self.Base = sqlalchemy.ext.declarative.declarative_base()
'''
The engine will also initialize on creation.
'''
self.initEngine()
# Initialize the engine
def initEngine(self):
self.engine = sqlalchemy.create_engine("sqlite:///%s" % self.fileName)
sqlalchemy_utils.create_database(self.engine.url) #sqlalchemy-utils
# I don't even know why I called base here
# I am just copying the question
self.Base.metadata.create_all(bind=self.engine) # declarative_base
# Set config. Note that more can be set here. This is just an example.
def setConfig(self, fileName):
self.fileName = fileName
'''
One the config is changed, it is probably preferable to reset the engine.
Should garbage collector be run here? Does this consume too much memory
so that it benificial to call the garbage collector?
'''
self.initEngine()
# These methods below are to demonstrate the singleton instance
def createTable(self):
conn = self.engine.connect()
conn.execute("create table if not exists x (a integer, b integer)")
conn.close()
def insertData(self):
conn = self.engine.connect()
conn.execute("insert into x (a, b) values (1, 1)")
conn.execute("insert into x (a, b) values (2, 2)")
conn.execute("insert into x (a, b) values (3, 3)")
conn.execute("insert into x (a, b) values (5, 5)")
conn.close()
def getData(self):
conn = self.engine.connect()
'''
Convert result into list otherwise can't work with result on a closed
connection
'''
result = [conn.execute('''
select * from x;
''').fetchall()]
conn.close()
return result
def getEngineUrl(self):
return self.engine.url
main2.py
import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from db import db
# NOTE THAT THIS IS THE BIGGEST DIFFERENT BETWEEN THE TWO SINGLETON
# EXAMPLE
database = db.__new__(db)
# Testing insert
def justTesting():
print("Currently working with engine " + str(database.getEngineUrl()))
database.insertData()
# Testing changing file
def changeFile():
database.setConfig("test2.sql")
database.createTable()
main.py:
import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from db import db
from main2 import *
# Creating the db object. It need to be initialize 1.
database = db("test1.sql")
# Create the database table from here
database.createTable()
# Insert data into the table from main2
# Using main2 to print the engine.url
justTesting()
# get the result
results = database.getData()
for result in results:
print(result)
# Change the database from main2
changeFile()
print("Currently working with engine " + str(database.getEngineUrl()))
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.