Sqlite3是Python3標(biāo)準(zhǔn)庫(kù)不需要另外安裝,只需要安裝SQLAlchemy即可。本文sqlalchemy版本為1.2.12
pip install sqlalchemy
除了第一步創(chuàng)建引擎時(shí)連接URL不一樣,其他操作其他mysql等數(shù)據(jù)庫(kù)和sqlite都是差不多的。
sqlite創(chuàng)建數(shù)據(jù)庫(kù)連接就是創(chuàng)建數(shù)據(jù)庫(kù),而其他mysql等應(yīng)該是需要數(shù)據(jù)庫(kù)已存在才能創(chuàng)建數(shù)據(jù)庫(kù)連接;建立數(shù)據(jù)庫(kù)連接本文中有時(shí)會(huì)稱為建立數(shù)據(jù)庫(kù)引擎。
以相對(duì)路徑形式,在當(dāng)前目錄下創(chuàng)建數(shù)據(jù)庫(kù)格式如下:
# sqlite://nohostname>/path> # where path> is relative: engine = create_engine('sqlite:///foo.db')
以絕對(duì)路徑形式創(chuàng)建數(shù)據(jù)庫(kù),格式如下:
#Unix/Mac - 4 initial slashes in total engine = create_engine('sqlite:////absolute/path/to/foo.db') #Windows engine = create_engine('sqlite:///C:\\path\\to\\foo.db') #Windows alternative using raw string engine = create_engine(r'sqlite:///C:\path\to\foo.db')
sqlite可以創(chuàng)建內(nèi)存數(shù)據(jù)庫(kù)(其他數(shù)據(jù)庫(kù)不可以),格式如下:
# format 1 engine = create_engine('sqlite://') # format 2 engine = create_engine('sqlite:///:memory:', echo=True)
PostgreSQL:
# default engine = create_engine('postgresql://scott:tiger@localhost/mydatabase') # psycopg2 engine = create_engine('postgresql+psycopg2://scott:tiger@localhost/mydatabase') # pg8000 engine = create_engine('postgresql+pg8000://scott:tiger@localhost/mydatabase')
# default engine = create_engine('mysql://scott:tiger@localhost/foo') # mysql-python engine = create_engine('mysql+mysqldb://scott:tiger@localhost/foo') # MySQL-connector-python engine = create_engine('mysql+mysqlconnector://scott:tiger@localhost/foo') # OurSQL engine = create_engine('mysql+oursql://scott:tiger@localhost/foo')
engine = create_engine('oracle://scott:tiger@127.0.0.1:1521/sidname') engine = create_engine('oracle+cx_oracle://scott:tiger@tnsname')
# pyodbc engine = create_engine('mssql+pyodbc://scott:tiger@mydsn') # pymssql engine = create_engine('mssql+pymssql://scott:tiger@hostname:port/dbname')
我們以在當(dāng)前目錄下創(chuàng)建foo.db為例,后續(xù)各步同使用此數(shù)據(jù)庫(kù)。
在create_engine中我們多加了兩樣?xùn)|西,一個(gè)是echo=Ture,一個(gè)是check_same_thread=False。
echo=Ture----echo默認(rèn)為False,表示不打印執(zhí)行的SQL語(yǔ)句等較詳細(xì)的執(zhí)行信息,改為T(mén)ure表示讓其打印。
check_same_thread=False----sqlite默認(rèn)建立的對(duì)象只能讓建立該對(duì)象的線程使用,而sqlalchemy是多線程的所以我們需要指定check_same_thread=False來(lái)讓建立的對(duì)象任意線程都可使用。否則不時(shí)就會(huì)報(bào)錯(cuò):sqlalchemy.exc.ProgrammingError: (sqlite3.ProgrammingError) SQLite objects created in a thread can only be used in that same thread. The object was created in thread id 35608 and this is thread id 34024. [SQL: 'SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password \nFROM users \nWHERE users.name = ?\n LIMIT ? OFFSET ?'] [parameters: [{}]] (Background on this error at: http://sqlalche.me/e/f405)
from sqlalchemy import create_engine engine = create_engine('sqlite:///foo.db?check_same_thread=False', echo=True)
先建立基本映射類,后邊真正的映射類都要繼承它
from sqlalchemy.ext.declarative import declarative_base Base = declarative_base()
然后創(chuàng)建真正的映射類,我們這里以一下User映射類為例,我們?cè)O(shè)置它映射到users表。
首先要明確,ORM中一般情況下表是不需要先存在的反而為了類與表對(duì)應(yīng)無(wú)誤借助通過(guò)映射類來(lái)創(chuàng)建;當(dāng)然表已戲存在了也無(wú)可以,在下一小結(jié)中你可以自己決定如果表存在時(shí)要如何操作是重新創(chuàng)建還是使用已有表,但使用已有表你需要確保和類的變量名與表的各字段名要對(duì)得上。
from sqlalchemy import Column, Integer, String # 定義映射類User,其繼承上一步創(chuàng)建的Base class User(Base): # 指定本類映射到users表 __tablename__ = 'users' # 如果有多個(gè)類指向同一張表,那么在后邊的類需要把extend_existing設(shè)為T(mén)rue,表示在已有列基礎(chǔ)上進(jìn)行擴(kuò)展 # 或者換句話說(shuō),sqlalchemy允許類是表的字集 # __table_args__ = {'extend_existing': True} # 如果表在同一個(gè)數(shù)據(jù)庫(kù)服務(wù)(datebase)的不同數(shù)據(jù)庫(kù)中(schema),可使用schema參數(shù)進(jìn)一步指定數(shù)據(jù)庫(kù) # __table_args__ = {'schema': 'test_database'} # 各變量名一定要與表的各字段名一樣,因?yàn)橄嗤拿质撬麄冎g的唯一關(guān)聯(lián)關(guān)系 # 從語(yǔ)法上說(shuō),各變量類型和表的類型可以不完全一致,如表字段是String(64),但我就定義成String(32) # 但為了避免造成不必要的錯(cuò)誤,變量的類型和其對(duì)應(yīng)的表的字段的類型還是要相一致 # sqlalchemy強(qiáng)制要求必須要有主鍵字段不然會(huì)報(bào)錯(cuò),如果要映射一張已存在且沒(méi)有主鍵的表,那么可行的做法是將所有字段都設(shè)為primary_key=True # 不要看隨便將一個(gè)非主鍵字段設(shè)為primary_key,然后似乎就沒(méi)報(bào)錯(cuò)就能使用了,sqlalchemy在接收到查詢結(jié)果后還會(huì)自己根據(jù)主鍵進(jìn)行一次去重 # 指定id映射到id字段; id字段為整型,為主鍵,自動(dòng)增長(zhǎng)(其實(shí)整型主鍵默認(rèn)就自動(dòng)增長(zhǎng)) id = Column(Integer, primary_key=True, autoincrement=True) # 指定name映射到name字段; name字段為字符串類形, name = Column(String(20)) fullname = Column(String(32)) password = Column(String(32)) # __repr__方法用于輸出該類的對(duì)象被print()時(shí)輸出的字符串,如果不想寫(xiě)可以不寫(xiě) def __repr__(self): return "User(name='%s', fullname='%s', password='%s')>" % ( self.name, self.fullname, self.password)
在上面的定義我__tablename__屬性是寫(xiě)死的,但有時(shí)我們可能想通過(guò)外部給類傳遞表名,此時(shí)可以通過(guò)以下變通的方法來(lái)實(shí)現(xiàn):
def get_dynamic_table_name_class(table_name): # 定義一個(gè)內(nèi)部類 class TestModel(Base): # 給表名賦值 __tablename__ = table_name __table_args__ = {'extend_existing': True} username = Column(String(32), primary_key=True) password = Column(String(32)) # 把動(dòng)態(tài)設(shè)置表名的類返回去 return TestModel
# 查看映射對(duì)應(yīng)的表 User.__table__ # 創(chuàng)建數(shù)據(jù)表。一方面通過(guò)engine來(lái)連接數(shù)據(jù)庫(kù),另一方面根據(jù)哪些類繼承了Base來(lái)決定創(chuàng)建哪些表 # checkfirst=True,表示創(chuàng)建表前先檢查該表是否存在,如同名表已存在則不再創(chuàng)建。其實(shí)默認(rèn)就是True Base.metadata.create_all(engine, checkfirst=True) # 上邊的寫(xiě)法會(huì)在engine對(duì)應(yīng)的數(shù)據(jù)庫(kù)中創(chuàng)建所有繼承Base的類對(duì)應(yīng)的表,但很多時(shí)候很多只是用來(lái)則試的或是其他庫(kù)的 # 此時(shí)可以通過(guò)tables參數(shù)指定方式,指示僅創(chuàng)建哪些表 # Base.metadata.create_all(engine,tables=[Base.metadata.tables['users']],checkfirst=True) # 在項(xiàng)目中由于model經(jīng)常在別的文件定義,沒(méi)主動(dòng)加載時(shí)上邊的寫(xiě)法可能寫(xiě)導(dǎo)致報(bào)錯(cuò),可使用下邊這種更明確的寫(xiě)法 # User.__table__.create(engine, checkfirst=True) # 另外我們說(shuō)這一步的作用是創(chuàng)建表,當(dāng)我們已經(jīng)確定表已經(jīng)在數(shù)據(jù)庫(kù)中存在時(shí),我完可以跳過(guò)這一步 # 針對(duì)已存放有關(guān)鍵數(shù)據(jù)的表,或大家共用的表,直接不寫(xiě)這創(chuàng)建代碼更讓人心里踏實(shí)
從上邊的討論可以知道,我們可以定義model然后根據(jù)model來(lái)創(chuàng)建數(shù)據(jù)表(當(dāng)然也可以不創(chuàng)建),那可不可以反過(guò)來(lái)根據(jù)已有的表來(lái)自動(dòng)生成model代碼呢,答案是可以的,使用sqlacodegen。
sqlacodegen安裝操作如下:
# 如果網(wǎng)絡(luò)通,直接pip安裝 pip install sqlacodegen # 如果網(wǎng)絡(luò)不通,先在網(wǎng)絡(luò)通的機(jī)器上使用pip下載sqlacodegen及期依賴包 pip download sqlacodegen # 上傳到真正要安裝的機(jī)器后再用pip安裝,依賴包也會(huì)自動(dòng)安裝。版本可能會(huì)變化改成自己具體的包名 pip install sqlacodegen-2.1.0-py2.py3-none-any.whl
sqlacodegen生成model操作如下:
# linux應(yīng)該被安裝在/usr/local/bin/sqlacodegen # mysql+pymysql示例 # 可使用--tables指定要生成model的表,不指定時(shí)為所有表都生成model # 可使用--outfile指定代碼輸出到的文件,不指定時(shí)輸出到stdout # 注意只有當(dāng)表有主鍵時(shí)sqlacodegen才生成如下的class,不然會(huì)使用舊的生成Table()類實(shí)例的形式 # 更多說(shuō)明可使用-h參看 sqlacodegen mysql+pymysql://user:password@localhost/dbname [--tables table_name1,table_name2] [--outfile model.py]
如我的一個(gè)示例操作如下,成功為指定表生成model:
增查改刪(CRUD)操作需要使用session進(jìn)行操作
from sqlalchemy.orm import sessionmaker # engine是2.2中創(chuàng)建的連接 Session = sessionmaker(bind=engine) # 創(chuàng)建Session類實(shí)例 session = Session()
# 創(chuàng)建User類實(shí)例 ed_user = User(name='ed', fullname='Ed Jones', password='edspassword') # 將該實(shí)例插入到users表 session.add(ed_user) # 一次插入多條記錄形式 session.add_all( [User(name='wendy', fullname='Wendy Williams', password='foobar'), User(name='mary', fullname='Mary Contrary', password='xxg527'), User(name='fred', fullname='Fred Flinstone', password='blah')] ) # 當(dāng)前更改只是在session中,需要使用commit確認(rèn)更改才會(huì)寫(xiě)入數(shù)據(jù)庫(kù) session.commit()
query將轉(zhuǎn)成select xxx from xxx部分,filter/filter_by將轉(zhuǎn)成where部分,limit/order by/group by分別對(duì)應(yīng)limit()/order_by()/group_by()方法。這句話非常的重要,理解后你將大量減少sql這么寫(xiě)那在sqlalchemy該怎么寫(xiě)的疑惑。
filter_by相當(dāng)于where部分,外另可用filter。他們的區(qū)別是filter_by參數(shù)寫(xiě)法類似sql形式,filter參數(shù)為python形式。
更多匹配寫(xiě)法見(jiàn):https://docs.sqlalchemy.org/en/13/orm/tutorial.html#common-filter-operators
our_user = session.query(User).filter_by(name='ed').first() our_user # 比較ed_user與查詢到的our_user是否為同一條記錄 ed_user is our_user # 只獲取指定字段 # 但要注意如果只獲取部分字段,那么返回的就是元組而不是對(duì)象了 # session.query(User.name).filter_by(name='ed').all() # like查詢 # session.query(User).filter(User.name.like("ed%")).all() # 正則查詢 # session.query(User).filter(User.name.op("regexp")("^ed")).all() # 統(tǒng)計(jì)數(shù)量 # session.query(User).filter(User.name.like("ed%")).count() # 調(diào)用數(shù)據(jù)庫(kù)內(nèi)置函數(shù) # 以count()為例,都是直接func.func_name()這種格式,func_name與數(shù)據(jù)庫(kù)內(nèi)的寫(xiě)法保持一致 # from sqlalchemy import func # session.query(func.count(User3.name)).one() # 字段名為字符串形式 # column_name = "name" # session.query(User).filter(User3.__table__.columns[column_name].like("ed%")).all() # 獲取執(zhí)行的sql語(yǔ)句 # 獲取記錄數(shù)的方法有all()/one()/first()等幾個(gè)方法,如果沒(méi)加這些方法,得到的只是一個(gè)將要執(zhí)行的sql對(duì)象,并沒(méi)真正提交執(zhí)行 # from sqlalchemy.dialects import mysql # sql_obj = session.query(User).filter_by(name='ed') # sql_command = sql_obj.statement.compile(dialect=mysql.dialect(), compile_kwargs={"literal_binds": True}) # sql_result = sql_obj.all()
另外要注意該鏈接Common Filter Operators節(jié)中形如equals的query.filter(User.name == 'ed'),在真正使用時(shí)都得改成session.query(User).filter(User.name == 'ed')形式,不然只后看到報(bào)錯(cuò)“NameError: name 'query' is not defined”。
我們上邊的sql直接是our_user = session.query(User).filter_by(name='ed').first()形式,但到實(shí)際中時(shí)User部分和name=‘ed'這部分是通過(guò)參數(shù)傳過(guò)來(lái)的,使用參數(shù)傳遞時(shí)就要注意以下兩個(gè)問(wèn)題。
首先,是參數(shù)不要使用引號(hào)括起來(lái)。比如如下形式是錯(cuò)誤的(使用引號(hào)),將報(bào)錯(cuò)sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column
table_and_column_name = "User" filter = "name='ed'" our_user = session.query(table_and_column_name).filter_by(filter).first()
其次,對(duì)于有等號(hào)參數(shù)需要變換形式。如下去掉了引號(hào),對(duì)table_and_column_name沒(méi)問(wèn)題,但filter = (name='ed')這種寫(xiě)法在python是不允許的
table_and_column_name = User # 下面這條語(yǔ)句不符合語(yǔ)法 filter = (name='ed') our_user = session.query(table_and_column_name).filter_by(filter).first()
對(duì)參數(shù)中帶等號(hào)的這種形式,現(xiàn)在能想到的只有使用filter代替filter_by,即將sql語(yǔ)句中的=號(hào)轉(zhuǎn)變?yōu)閜ython語(yǔ)句中的==。正確寫(xiě)法如下:
table_and_column_name = User filter = (User.name=='ed') our_user = session.query(table_and_column_name).filter(filter).first()
# 要修改需要先將記錄查出來(lái) mod_user = session.query(User).filter_by(name='ed').first() # 將ed用戶的密碼修改為modify_paswd mod_user.password = 'modify_passwd' # 確認(rèn)修改 session.commit() # 但是上邊的操作,先查詢?cè)傩薷南喈?dāng)于執(zhí)行了兩條語(yǔ)句,和我們印象中的update不一致 # 可直接使用下邊的寫(xiě)法,傳給服務(wù)端的就是update語(yǔ)句 # session.query(User).filter_by(name='ed').update({User.password: 'modify_passwd'}) # session.commit() # 以同schema的一張表更新另一張表的寫(xiě)法 # 在跨表的update/delete等函數(shù)中synchronize_session=False一定要有不然報(bào)錯(cuò) # session.query(User).filter_by(User.name=User1.name).update({User.password: User2.password}, synchronize_session=False) # 以一schema的表更新另一schema的表的寫(xiě)法 # 寫(xiě)法與同一schema的一樣,只是定義model時(shí)需要使用__table_args__ = {'schema': 'test_database'}等形式指定表對(duì)應(yīng)的schema
# 要?jiǎng)h除需要先將記錄查出來(lái) del_user = session.query(User).filter_by(name='ed').first() # 打印一下,確認(rèn)未刪除前記錄存在 del_user # 將ed用戶記錄刪除 session.delete(del_user) # 確認(rèn)刪除 session.commit() # 遍歷查看,已無(wú)ed用戶記錄 for user in session.query(User): print(user) # 但上邊的寫(xiě)法,先查詢?cè)賱h除,相當(dāng)于給mysql服務(wù)端發(fā)了兩條語(yǔ)句,和我們印象中的delete語(yǔ)句不一致 # 可直接使用下邊的寫(xiě)法,傳給服務(wù)端的就是delete語(yǔ)句 # session.query(User).filter_by(name='ed').first().delete()
雖然使用框架規(guī)定形式可以在一定程度上解決各數(shù)據(jù)庫(kù)的SQL差異,比如獲取前兩條記錄各數(shù)據(jù)庫(kù)形式如下。
# mssql/access select top 2 * from table_name; # mysql select * from table_name limit 2; # oracle select * from table_name where rownum = 2;
但框架存消除各數(shù)據(jù)庫(kù)SQL差異的同時(shí)會(huì)引入各框架CRUD的差異,而開(kāi)發(fā)人員往往就有一定的SQL基礎(chǔ),如果一個(gè)框架強(qiáng)制用戶只能使用其規(guī)定的CRUD形式那反而增加用戶的學(xué)習(xí)成本,這個(gè)框架注定不能成為成功的框架。直接地執(zhí)行SQL而不是使用框架設(shè)定的CRUD雖然不是一種被鼓勵(lì)的操作但也不應(yīng)被視為一種見(jiàn)不得人的行為。
# 正常的SQL語(yǔ)句 sql = "select * from users" # sqlalchemy使用execute方法直接執(zhí)行SQL records = session.execute(sql)
更多關(guān)于Python3 SQLAlchemy Sqlite3相關(guān)教程請(qǐng)查看下面的相關(guān)鏈接
標(biāo)簽:平頂山 哈爾濱 大慶 烏蘭察布 郴州 烏蘭察布 合肥 海南
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Python3+SQLAlchemy+Sqlite3實(shí)現(xiàn)ORM教程》,本文關(guān)鍵詞 Python3+SQLAlchemy+Sqlite3,實(shí)現(xiàn),;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。