注意:在flask中,操做數據庫仍是經過orm調用驅動來操做。sqlalchemy是python下的一款工業級的orm,比Django自帶的orm要強大不少,至於什麼類型的數據庫不重要,經過orm上層操做的代碼是同樣的。咱們目前介紹的是sqlalchemy,與flask沒有太大關係,後面會介紹如何將sqlalchemy與flask集成,也有一個開源的第三方組件,叫flask-sqlalchemy。 關於數據庫,我這裏選擇的是postgresql,固然mysql、Oracle、Mariadb等數據庫也是能夠的。
安裝postgresql能夠直接到這個網站下載,https://www.enterprisedb.com/downloads/postgres-postgresql-downloads,我這裏下載的是Windows版本的。
html
直接去下載便可,一路next便可。我已經安裝過了,這裏就再也不演示了。
java
使用navicat進行鏈接是沒問題的
python
sqlalchemy是一款orm框架
注意:SQLAlchemy自己是沒法處理數據庫的,必須依賴於第三方插件,比方說pymysql,cx_Oracle等等
SQLAlchemy等因而一種更高層的封裝,裏面封裝了許多dialect(至關因而字典),定義了一些組合,比方說: 能夠用pymysql處理mysql,也能夠用cx_Oracle處理Oracle,關鍵是程序員使用什麼,而後會在dialect裏面進行匹配,同時也將咱們高層定義的類轉化成sql語句,而後交給對應的DBapi去執行。
除此以外,SQLAlchemy還維護一個數據庫鏈接池,數據庫的連接和斷開是很是耗時的 SQLAlchemy維護了一個數據庫鏈接池,那麼就能夠拿起來直接用mysql
首先要安裝sqlalchemy和psycopg2(python中用於鏈接postgresql的驅動),直接pip install 便可
linux
# 鏈接數據庫須要建立引擎 from sqlalchemy import create_engine username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 # 建立一個引擎,能夠用來執行sql語句 # 鏈接方式:數據庫+驅動://用戶名:密碼@ip:端口/數據庫 # 固然驅動也能夠不指定,會選擇默認的,若是有多種驅動的話,想選擇具體的一種,能夠指定。 # 但對於目前的postgresql來講,不指定驅動,默認就是psycopg2 # 這裏去掉driver的話,也能夠這麼寫engine = create_engine(f"{dialect}://{username}:{password}@{hostname}:{port}/{database}") engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") create_table_sql = """ create table girls( name varchar(255), age int, gender varchar(1) ); comment on column girls.name is '少女的姓名'; comment on column girls.age is '少女的年齡'; comment on column girls.gender is '少女的性別'; """ engine.execute(create_table_sql)
顯然是執行成功的,而後將表刪掉也是沒問題的。
git
orm: object relation mapping,就是能夠把咱們寫的類轉化成表。將類裏面的元素映射到數據庫表裏面程序員
剛纔咱們已經成功的建立了一張表了,可是咱們發現實際上咱們寫的仍是sql語句,只不過是用python來執行的。換句話說,我sql語句寫好,我還能夠在終端、Navicat等等地方執行。web
下面,咱們將使用python,經過定義一個類,而後經過sqlalchemy,映射爲數據庫中的一張表。sql
from sqlalchemy import create_engine username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") # 須要如下步驟 ''' 1.建立一個orm模型,這個orm模型必須繼承SQLAlchemy給咱們提供好的基類 2.在orm中建立一些屬性,來跟表中的字段進行一一映射,這些屬性必須是SQLAlchemy給咱們提供好的數據類型 3.將建立好的orm模型映射到數據庫中 ''' # 1.生成一個基類,而後定義模型(也就是orm中的o),繼承這個基類 from sqlalchemy.ext.declarative import declarative_base # 這個declarative_base是一個函數,傳入engine,生成基類 Base = declarative_base(bind=engine) class Girl(Base): # 指定表名:__tablename__ __tablename__ = "girls" # 指定schema,postgresql是由schema的,如何指定呢 __table_args__ = { "schema": "public" # 其實若是不指定,那麼public默認是public,這裏爲了介紹,因此寫上 } # 2.建立屬性,來和數據庫表中的字段進行映射 from sqlalchemy import Column, Integer, VARCHAR # 要以 xx = Column(type)的形式,那麼在表中,字段名就叫xx,類型就是type id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(255)) age = Column(Integer) gender = Column(VARCHAR(1)) class Boy(Base): __tablename__ = "boys" from sqlalchemy import Column, Integer, VARCHAR id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(255)) age = Column(Integer) gender = Column(VARCHAR(1)) # 3.模型建立完畢,那麼將模型映射到數據庫中 # 這行代碼就表示將全部繼承了Base的類映射到數據庫中變成表,咱們這裏有兩個類。不出意外的話,數據庫中應該會有兩張表,叫girls和boys Base.metadata.create_all() # 一旦映射,即便改變模型以後再次執行,也不會再次映射。 # 好比,我在模型中新增了一個字段,再次映射,數據庫中的表是不會多出一個字段的 # 除非將數據庫中與模型對應的表刪了,從新映射。 # 若是模型修改了,能夠調用Base.metadata.drop_all(),而後再create_all()
將表刪除,從新映射
數據庫
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR from sqlalchemy.orm import sessionmaker username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class Girls(Base): __tablename__ = "girls" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(30)) age = Column(Integer) gender = Column(VARCHAR(1)) anime = Column(VARCHAR(50)) Base.metadata.create_all() # 將對象實例化,而後對對象進行操做,會映射爲對數據庫表的操做 girls = Girls() # 建立Session類,綁定引擎 Session = sessionmaker(bind=engine) # 實例化獲得session對象,經過session來操做數據庫的表 session = Session()
girls.name = "satori" girls.age = 16 girls.gender = "f" girls.anime = "動漫地靈殿" # 一個Girls實例在數據庫的表中對應一條記錄,進行添加 session.add(girls) # 將對girls作的操做提交執行 # 像增、刪、改這些設計數據變動的操做,必需要提交 session.commit() # 也能夠批量進行操做 # 另外能夠生成Girls實例,獲得girls,經過girls.xx的方式賦值 # 也能夠經過Girl(xx="")的方式 session.add_all([ Girls(name="koishi", age=15, gender="f", anime="東方地靈殿"), Girls(name="mashiro", age=16, gender="f", anime="櫻花莊的寵物女孩"), Girls(name="matsuri", age=400, gender="f", anime="sola"), Girls(name="kurisu", age=20, gender="f", anime="命運石之門") ]) session.commit()
# 調用session.query(Girls),注意裏面傳入的不是數據庫的表名,而是模型。 # 映射到數據庫至關於select * from girls # 但獲得是一個query對象,還能夠加上filter進行過濾,至關於where,獲得了還是一個query對象 # 對query對象調用first()會拿到第一條數據,沒有的話爲None。調用all()會拿到全部知足條件的數據,沒有的話則是一個空列表 query = session.query(Girls).filter(Girls.name == "satori") print(type(query)) # <class 'sqlalchemy.orm.query.Query'> # 直接打印query對象,會生成對應的sql語句 print(query) """ SELECT girls.id AS girls_id, girls.name AS girls_name, girls.age AS girls_age, girls.gender AS girls_gender, girls.anime AS girls_anime FROM girls WHERE girls.name = %(name_1)s """ # 調用first()或者all(),則能夠獲取具體的Girls類的對象。裏面的屬性,如:name、age等等則存放了數據庫表中取的值。 # 可是咱們沒有寫__str__方法,因此打印的是一個類對象 print(query.first()) # <__main__.Girls object at 0x000000000AF57908> # 咱們能夠手動查找屬性 girl1 = query.first() print(girl1.name) # satori print(girl1.age) # 16 print(girl1.gender) # f print(girl1.anime) # 動漫地靈殿
# 改和查比較相似,首先要查想要修改的記錄 # 熟悉東方的小夥伴會發現,我手癌把東方地靈殿寫成動漫地靈殿了,咱們這裏改回來 # 將數據庫的對應記錄獲取出來了。 """ 以前說過,Girls這個模型對應數據庫的一張表 Girls的一個實例對應數據庫表中的一條記錄 那麼反過來同理,數據庫表中的一條記錄對應Girls的一個實例 咱們經過修改實例的屬性,再同步回去,就能夠反過來修改數據庫表中的記錄 """ # 獲取相應記錄對應的實例 girl2 = session.query(Girls).filter(Girls.anime == "動漫地靈殿").first() # 修改屬性 girl2.anime = "東方地靈殿" # 而後提交,因爲是改,因此不須要add # 而後girl2這個實例就會映射回去,從而把對應字段修改 session.commit()
順序無所謂啦,至少記錄已經被咱們修改了
# 篩選出要刪除的字段 girl = session.query(Girls).filter(Girls.gender == "f").all() for g in girl: # 調用session.delete session.delete(g) # 一樣須要提交 session.commit()
能夠看到,數據庫裏面的數據全沒了。爲了後面介紹其餘方法,咱們再將數據從新寫上去。
模型,指定查找這個模型中全部的對象
模型中的屬性,能夠指定查找模型的某幾個屬性
聚合函數
func.count:統計行的數量 func.avg:求平均值 func.max:求最大值 func.min:求最小值 func.sum:求總和
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, func from sqlalchemy.orm import sessionmaker username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class Girls(Base): __tablename__ = "girls" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(30)) age = Column(Integer) gender = Column(VARCHAR(1)) anime = Column(VARCHAR(50)) def __str__(self): return f"{self.name}-{self.age}-{self.gender}-{self.anime}" Base.metadata.create_all() # 建立Session類,綁定引擎 Session = sessionmaker(bind=engine) # 實例化獲得session對象,經過session來操做數據庫的表 session = Session() query = session.query(Girls).all() print(query) """ [<__main__.Girls object at 0x000000000AF9F5C0>, <__main__.Girls object at 0x000000000AF9F630>, <__main__.Girls object at 0x000000000AF9F6A0>, <__main__.Girls object at 0x000000000AF9F710>, <__main__.Girls object at 0x000000000AF9F7B8>] """ for _ in query: print(_) """ satori-16-f-動漫地靈殿 koishi-15-f-東方地靈殿 mashiro-16-f-櫻花莊的寵物女孩 matsuri-400-f-sola kurisu-20-f-命運石之門 """ # 若是我不想一會兒查出全部的字段,而是隻要某幾個字段,該怎麼辦呢? # 只寫調用模型.屬性便可,那麼到數據庫就會選擇相應的字段 print(session.query(Girls.name, Girls.age).all()) # [('satori', 16), ('koishi', 15), ('mashiro', 16), ('matsuri', 400), ('kurisu', 20)] # 聚合函數 # 獲得的query對象,直接打印是sql語句,加上first取第一個數據 print(session.query(func.count(Girls.name)).first()) # (5,) print(session.query(func.avg(Girls.age)).first()) # (Decimal('93.4000000'),) print(session.query(func.max(Girls.age)).first()) # (400,) print(session.query(func.min(Girls.age)).first()) # (15,) print(session.query(func.sum(Girls.age)).first()) # (467,)
爲了演示,我將表刪除從新建立,添加新的數據
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, and_, or_ from sqlalchemy.orm import sessionmaker username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class Girls(Base): __tablename__ = "girls" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(30)) age = Column(Integer) gender = Column(VARCHAR(1)) anime = Column(VARCHAR(50)) def __str__(self): return f"{self.name}-{self.age}-{self.gender}-{self.anime}" Base.metadata.create_all() # 建立Session類,綁定引擎 Session = sessionmaker(bind=engine) # 實例化獲得session對象,經過session來操做數據庫的表 session = Session() # 1. == print(session.query(Girls).filter(Girls.age == 16).first()) """ 古明地覺-16-f-動漫地靈殿 """ # 2.!= print(session.query(Girls).filter(Girls.age != 16).first()) """ 四方茉莉-400-f-sola """ # 3.like,和sql裏面的like同樣。除此以外,還有ilike,表示不區分大小寫 print([str(q) for q in session.query(Girls).filter(Girls.name.like("%宮%")).all()]) """ ['森宮蒼乃-20-f-sola', '雨宮優子-16-f-悠久之翼', '宮村宮子-15-f-悠久之翼'] """ print([str(q) for q in session.query(Girls).filter(Girls.name.like("_宮%")).all()]) """ ['森宮蒼乃-20-f-sola', '雨宮優子-16-f-悠久之翼'] """ # 4.in_,至於爲何多了多了一個_,這個bs4相似,爲了不和python裏的關鍵字衝突。 print([str(q) for q in session.query(Girls).filter(Girls.age.in_([16, 400])).all()]) """ ['古明地覺-16-f-動漫地靈殿', '椎名真白-16-f-櫻花莊的寵物女孩', '四方茉莉-400-f-sola', '春日野穹-16-f-緣之空', '雨宮優子-16-f-悠久之翼'] """ # 5.notin_, Girls.age.notin_也等價於~Girls.age.in_ print([str(q) for q in session.query(Girls).filter(Girls.age.notin_([16, 20, 400])).all()]) """ ['立華奏-18-f-angelbeats', '古河渚-19-f-Clannad', '阪上智代-18-f-Clannad', '古明地戀-15-f-東方地靈殿', '宮村宮子-15-f-悠久之翼'] """ # 6.isnull print(session.query(Girls).filter(Girls.age is None).first()) """ None """ # 7.isnotnull print(session.query(Girls).filter(Girls.age is not None).first()) """ 古明地覺-16-f-動漫地靈殿 """ # 8.and_, from sqlachemy import and_ print(session.query(Girls).filter(and_(Girls.age == 16, Girls.anime == "櫻花莊的寵物女孩")).first()) """ 椎名真白-16-f-櫻花莊的寵物女孩 """ # 9.or_, from sqlalchemy import or_ print([str(q) for q in session.query(Girls).filter(or_(Girls.age == 16, Girls.anime == "悠久之翼")).all()]) """ ['古明地覺-16-f-動漫地靈殿', '椎名真白-16-f-櫻花莊的寵物女孩', '春日野穹-16-f-緣之空', '雨宮優子-16-f-悠久之翼', '宮村宮子-15-f-悠久之翼'] """ # 10.count,統計數量 print(session.query(Girls).filter(Girls.age == 16).count()) # 4 # 11.切片 for g in session.query(Girls).filter(Girls.age == 16)[1: 3]: print(g) """ 椎名真白-16-f-櫻花莊的寵物女孩 春日野穹-16-f-緣之空 """ # 12.startswith for g in session.query(Girls).filter(Girls.anime.startswith("悠久")): print(g) """ 雨宮優子-16-f-悠久之翼 宮村宮子-15-f-悠久之翼 """ # 13.endswith for g in session.query(Girls).filter(Girls.anime.endswith("孩")): print(g) """ 椎名真白-16-f-櫻花莊的寵物女孩 """ # 14.+ - * / for g in session.query(Girls.name, Girls.age, Girls.age+3).filter((Girls.age + 3) >= 20).all(): print(g) """ ('四方茉莉', 400, 403) ('牧瀨紅莉棲', 20, 23) ('立華奏', 18, 21) ('古河渚', 19, 22) ('阪上智代', 18, 21) ('森宮蒼乃', 20, 23) """ # 15.concat for g in session.query(Girls.name, Girls.anime.concat('a').concat('b')).all(): print(g) """ ('古明地覺', '動漫地靈殿ab') ('椎名真白', '櫻花莊的寵物女孩ab') ('四方茉莉', 'solaab') ('牧瀨紅莉棲', '命運石之門ab') ('春日野穹', '緣之空ab') ('立華奏', 'angelbeatsab') ('古河渚', 'Clannadab') ('阪上智代', 'Clannadab') ('古明地戀', '東方地靈殿ab') ('森宮蒼乃', 'solaab') ('雨宮優子', '悠久之翼ab') ('宮村宮子', '悠久之翼ab') """ # 16.between,等價於and_(Girls.age >= 17, Girls.age <= 21) for g in session.query(Girls).filter(Girls.age.between(17, 21)).all(): print(g) """ 牧瀨紅莉棲-20-f-命運石之門 立華奏-18-f-angelbeats 古河渚-19-f-Clannad 阪上智代-18-f-Clannad 森宮蒼乃-20-f-sola """ # 17.contains,等價於Girls.anime.like("%la%") for g in session.query(Girls).filter(Girls.anime.contains("la")): print(g) """ 四方茉莉-400-f-sola 古河渚-19-f-Clannad 阪上智代-18-f-Clannad 森宮蒼乃-20-f-sola """ # 18.distinct for g in session.query(Girls.anime.distinct()): print(g) """ ('angelbeats',) ('sola',) ('悠久之翼',) ('緣之空',) ('東方地靈殿',) ('命運石之門',) ('Clannad',) ('動漫地靈殿',) ('櫻花莊的寵物女孩',) """
在mysql、postgresql等數據庫中,外鍵可使表之間的關係更加緊密。而SQLAlchemy中也一樣支持外鍵,經過ForforeignKey來實現,而且能夠指定表的外鍵約束
外鍵約束有如下幾種:
既然如此的話,咱們是否是要有兩張表啊,我這裏新建立兩張表
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey from sqlalchemy.orm import sessionmaker username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) # 我如今新建兩張表 # 表People--表language ==》父表--子表 class People(Base): __tablename__ = "People" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) # 不可爲空 age = Column(Integer, nullable=False) class Language(Base): __tablename__ = "Language" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) birthday = Column(Integer, nullable=False) type = Column(VARCHAR(20), nullable=False) # 還記得約束的種類嗎 # 1.RESTRICT:父表刪除數據,會阻止。 ''' 由於在子表中,引用了父表的數據,若是父表的數據刪除了,那麼字表就會懵逼,不知道該找誰了。 ''' # 2.NO ACTION ''' 和RESTRICT做用相似 ''' # 3.CASCADE ''' 級聯刪除:Language表的pid關聯了People表的id。若是People中id=1的記錄被刪除了,那麼Language中對應的pid=1的記錄也會被刪除 就是咱們關聯了,個人pid關聯了你的id,若是你刪除了一條記錄,那麼根據你刪除記錄的id,我也會相應的刪除一條,要死一塊死。 ''' # 4.SET NULL ''' 設置爲空:和CASCADE相似,就是個人pid關聯你的id,若是id沒了,那麼pid會被設置爲空,不會像CASCADE那樣,把相應pid所在整條記錄都給刪了 ''' # 這個pid關聯的是People表裏面的id,因此要和People表裏的id屬性保持一致 # 注意這裏的People是表名,不是咱們的類名,怪我,把兩個寫成同樣的了 pid = Column(Integer, ForeignKey("People.id", ondelete="RESTRICT")) # 引用的表.引用的字段,約束方式 Base.metadata.create_all() # 而後添加幾條記錄吧 Session = sessionmaker(bind=engine) session = Session() session.add_all([People(name="Guido van Rossum", age=62), People(name="Dennis Ritchie", age=77), People(name="James Gosling", age=63), Language(name="Python", birthday=1991, type="解釋型", pid=1), Language(name="C", birthday=1972, type="編譯型", pid=2), Language(name="Java", birthday=1995, type="解釋型", pid=3)]) session.commit()
咱們下面刪除數據,直接在Navicat裏面演示
顯示沒法刪除,由於在Language表中,pid關聯了該表的id。而pid和id保持了一致,因此沒法刪除。換句話說,若是這裏id=1,在字表中也有pid=1,那麼這裏的記錄是沒法刪除的。
若是將字表中pid=1的記錄進行修改,把pid=1改爲pid=10,這樣父表中id=1,在子表就沒有pid與之對應了。可是:
剩下的便再也不演示了,都是比較相似的。另外,若是再ForeignKey中不指定ondelete,那麼默認就是RESTRICT
當一張表關聯了另外一張表的外鍵,咱們能夠根據子表中pid,從而找到父表與之對應的id的所在的記錄。 可是有沒有方法,可以直接經過子表來查詢父表的內容呢?可使用relationship
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey from sqlalchemy.orm import sessionmaker, relationship username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class People(Base): # 這裏把表名改爲小寫,否則和類名同樣,容易引發歧義 __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) # 不可爲空 age = Column(Integer, nullable=False) class Language(Base): __tablename__ = "language" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) birthday = Column(Integer, nullable=False) type = Column(VARCHAR(20), nullable=False) # 這裏的people.id指定的是數據庫中people表的id字段 pid = Column(Integer, ForeignKey("people.id", ondelete="RESTRICT")) # 若是我想經過Language表查詢People表的數據呢? # 能夠經過relationship進行關聯,表示關聯的是數據庫中的people表 # 而後取出language表中的記錄,即可以經過 .父表.xxx 去取people表中的內容 # 這裏的填入的再也不是people表的表名了,而是對應的模型,也就是咱們定義的People這個類,以字符串的形式 # 由於咱們打印值,確定是將數據庫表的記錄變成模型的實例來獲取並打印的。 父表 = relationship("People") # 刪了從新建立 Base.metadata.drop_all() Base.metadata.create_all() # 而後添加幾條記錄吧 Session = sessionmaker(bind=engine) session = Session() session.add_all([People(name="Guido van Rossum", age=62), People(name="Dennis Ritchie", age=77), People(name="James Gosling", age=63), Language(name="Python", birthday=1991, type="解釋型", pid=1), Language(name="C", birthday=1972, type="編譯型", pid=2), Language(name="Java", birthday=1995, type="解釋型", pid=3)]) session.commit() for obj in session.query(Language).all(): # obj獲取的即是language表中的記錄,能夠找到在父表中id與字表的pid相對應的記錄 print(obj.父表.name, "發明了", obj.name) """ Guido van Rossum 發明了 Python Dennis Ritchie 發明了 C James Gosling 發明了 Java """
目前數據是一對一的,咱們也能夠一對多
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey from sqlalchemy.orm import sessionmaker, relationship username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class People(Base): # 這裏把表名改爲小寫,否則和類名同樣,容易引發歧義 __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) # 不可爲空 age = Column(Integer, nullable=False) # 在父表中關聯子表 子表 = relationship("Language") def __str__(self): return f"{self.name}--{self.age}--{self.字表}" class Language(Base): __tablename__ = "language" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) birthday = Column(Integer, nullable=False) type = Column(VARCHAR(20), nullable=False) # 這裏的people.id指定的是數據庫中people表的id字段 pid = Column(Integer, ForeignKey("people.id", ondelete="RESTRICT")) 父表 = relationship("People") def __str__(self): return f"{self.name}--{self.birthday}--{self.type}" Session = sessionmaker(bind=engine) session = Session() for obj in session.query(Language).all(): print(obj.父表.name, "發明了", obj.name) """ Guido van Rossum 發明了 python Dennis Ritchie 發明了 C James Gosling 發明了 java Dennis Ritchie 發明了 unix """ # 咱們來遍歷父表,那麼因爲我在父表中關聯了字表 # 那麼也能夠經過父表來找到字表 for obj in session.query(People).all(): print(obj.子表) """ [<__main__.Language object at 0x000000000AFF4278>] [<__main__.Language object at 0x000000000AFF4320>, <__main__.Language object at 0x000000000AFF4390>] [<__main__.Language object at 0x000000000AFF4438>] """ # 由於父表的id是惟一的,不會出現字表的一條記錄對應父表中多條記錄 # 可是反過來是徹底能夠的,所以這裏打印的是一個列表,即使只有一個元素,仍是以列表的形式打印 for obj in session.query(People).all(): for o in obj.子表: print(o) """ python--1991--解釋型 C--1972--編譯型 unix--1973--操做系統 java--1995--解釋型 """
能夠看到,咱們能經過在子表中定義relationship("父表模型"),這樣查詢子表,也能夠經過子表來看父表的記錄。那麼同理,我也能夠在父表中定義relations("子表模型"),查詢父表,也能夠經過父表來看子表的記錄
可是這樣仍是有點麻煩,因此在relationship中還有一個反向引用
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey from sqlalchemy.orm import sessionmaker, relationship username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class People(Base): # 這裏把表名改爲小寫,否則和類名同樣,容易引發歧義 __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) # 不可爲空 age = Column(Integer, nullable=False) # 我把這行代碼註釋掉了 # 子表 = relationship("Language") def __str__(self): return f"{self.name}--{self.age}" class Language(Base): __tablename__ = "language" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) birthday = Column(Integer, nullable=False) type = Column(VARCHAR(20), nullable=False) # 這裏的people.id指定的是數據庫中people表的id字段 pid = Column(Integer, ForeignKey("people.id", ondelete="RESTRICT")) # 我在子表(或者說子表對應的模型)的relationship中指定了backref="我屮艸芔茻" # 就等價於在父表對應的模型中,指定了 我屮艸芔茻=relationship("Language") 父表 = relationship("People", backref="我屮艸芔茻") def __str__(self): return f"{self.name}--{self.birthday}--{self.type}" Session = sessionmaker(bind=engine) session = Session() for obj in session.query(Language).all(): print(obj.父表.name, "發明了", obj.name) """ Guido van Rossum 發明了 python Dennis Ritchie 發明了 C James Gosling 發明了 java Dennis Ritchie 發明了 unix """ for obj in session.query(People).all(): for o in obj.我屮艸芔茻: print(o) """ python--1991--解釋型 C--1972--編譯型 unix--1973--操做系統 java--1995--解釋型 """
依舊是能夠訪問成功的
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey from sqlalchemy.orm import sessionmaker, relationship username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class People(Base): # 這裏把表名改爲小寫,否則和類名同樣,容易引發歧義 __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) # 不可爲空 age = Column(Integer, nullable=False) # 我把這行代碼註釋掉了 # 子表 = relationship("Language") def __str__(self): return f"{self.name}--{self.age}" class Language(Base): __tablename__ = "language" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) birthday = Column(Integer, nullable=False) type = Column(VARCHAR(20), nullable=False) # 這裏的people.id指定的是數據庫中people表的id字段 pid = Column(Integer, ForeignKey("people.id", ondelete="RESTRICT")) # 我在子表(或者說子表對應的模型)的relationship中指定了backref="我屮艸芔茻" # 就等價於在父表對應的模型中,指定了 我屮艸芔茻=relationship("Language") 父表 = relationship("People", backref="我屮艸芔茻") def __str__(self): return f"{self.name}--{self.birthday}--{self.type}" Session = sessionmaker(bind=engine) session = Session() # 在父表people添加一條屬性 # 在子表language中添加兩條屬性 people = People(id=666, name="KenThompson", age=75) language1 = Language(id=5, name="B", birthday=1968, type="編譯型", pid=666) language2 = Language(id=6, name="Go", birthday=2009, type="編譯型", pid=666) # 因爲People和Language是關聯的,而且經過"people.我屮艸芔茻"能夠訪問到Language表的屬性 # 那麼能夠經過people.我屮艸芔茻.append將Language對象添加進去 people.我屮艸芔茻.append(language1) people.我屮艸芔茻.append(language2) # 那麼我只須要提交people便可,會自動提交language1和language2 session.add(people) session.commit()
能夠看到添加people的同時,language1和language2也被成功地添加進去了
經過往父表添加記錄的同時,把子表的記錄也添進去叫作正向添加。同理,若是在往子表添加記錄的時候,把父表的記錄也添加進去,叫作反向添加
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey from sqlalchemy.orm import sessionmaker, relationship username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class People(Base): # 這裏把表名改爲小寫,否則和類名同樣,容易引發歧義 __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) # 不可爲空 age = Column(Integer, nullable=False) # 我把這行代碼註釋掉了 # 子表 = relationship("Language") def __str__(self): return f"{self.name}--{self.age}" class Language(Base): __tablename__ = "language" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) birthday = Column(Integer, nullable=False) type = Column(VARCHAR(20), nullable=False) # 這裏的people.id指定的是數據庫中people表的id字段 pid = Column(Integer, ForeignKey("people.id", ondelete="RESTRICT")) # 我在子表(或者說子表對應的模型)的relationship中指定了backref="我屮艸芔茻" # 就等價於在父表對應的模型中,指定了 我屮艸芔茻=relationship("Language") 父表 = relationship("People", backref="我屮艸芔茻") def __str__(self): return f"{self.name}--{self.birthday}--{self.type}" Session = sessionmaker(bind=engine) session = Session() people = People(id=7, name="松本行弘", age=53) language = Language(id=2333, name="ruby", birthday=1995, type="解釋型", pid=7) """ 父表 = relationship("People", backref="我屮艸芔茻") 正向添加: people.我屮艸芔茻.append(language) 反向添加: language.父表 = people """ language.父表 = people # 只須要添加language便可 session.add(language) session.commit()
記錄依舊能夠添加成功
因爲父表和字表能夠是一對多,咱們經過父表來查詢子表那麼獲得的是一個列表,哪怕只有一個元素,獲得的依舊是一個列表。能夠若是咱們已經知道只有一對一,不會出現一對多的狀況,所以在獲取經過父表獲取子表記錄的時候,獲得的是一個值,而再也不是隻有一個元素的列表,該怎麼辦呢? from sqlalchemy.orm import backref 父表 = relationship("People", backref=backref("我屮艸芔茻", uselist=False)) 這時候添加數據的時候,就再也不使用people.我屮艸芔茻.append(language)來添加了,由於是一對一。而是直接使用 people.我屮艸芔茻 = language來添加便可。
然而現實中,不少都是多對多的關係。好比博客園的文章,一個標籤下可能會有多篇文章,同理一篇文章也可能會有多個標籤。
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) # 既然要實現多對多,確定要藉助第三張表 # sqlalchemy已經爲咱們提供了一個Table,讓咱們去使用 Article_Tag = Table("article_tag", Base.metadata, # 一個列叫作article_id,關聯article裏面的id字段 Column("article_id", Integer, ForeignKey("article.id"), primary_key=True), # 一個列叫作tag_id,關聯tag裏面的id字段 Column("tag_id", Integer, ForeignKey("tag.id"), primary_key=True) ) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) # 只須要在一個模型中,定義relationship便可,由於有反向引用。 # 經過Article().tags拿到對應的[tag1, tag2...],也能夠經過Tag().articles拿到對應的[article1, article2] tags = relationship("Tag", backref="articles", secondary=Article_Tag) """ 或者在Tag中定義articles = relationship("Article", backref="tags", secondary=Article_Tag) 同樣的道理,可是必定要指定中間表,secondary=Article_Tags """ class Tag(Base): __tablename__ = "tag" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(50), nullable=False) """ 總結一下: 1.先把兩個須要多對多的模型創建出來 2.使用Table定義一箇中間表,參數是:表名、Base.metadata、關聯一張表的id的列、關聯另外一張表的id的列。而且都作爲主鍵 3.在兩個須要作多對多的模型中隨便選擇一個模型,定義一個relationship屬性,來綁定三者之間的關係,在使用relationship的時候,須要傳入一個secondary="中間表" """ Base.metadata.create_all()
添加數據
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) Article_Tag = Table("article_tag", Base.metadata, Column("article_id", Integer, ForeignKey("article.id"), primary_key=True), Column("tag_id", Integer, ForeignKey("tag.id"), primary_key=True) ) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) tags = relationship("Tag", backref="articles", secondary=Article_Tag) def __str__(self): return f"{self.title}" class Tag(Base): __tablename__ = "tag" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(50), nullable=False) def __str__(self): return f"{self.name}" Base.metadata.create_all() article1 = Article(title="article1") article2 = Article(title="article2") tag1 = Tag(name="tag1") tag2 = Tag(name="tag2") # 每一篇文章,添加兩個標籤 article1.tags.append(tag1) article1.tags.append(tag2) article2.tags.append(tag1) article2.tags.append(tag2) Session = sessionmaker(bind=engine) session = Session() # 只需添加article便可,tag會被自動添加進去 session.add_all([article1, article2]) session.commit()
獲取數據
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) Article_Tag = Table("article_tag", Base.metadata, Column("article_id", Integer, ForeignKey("article.id"), primary_key=True), Column("tag_id", Integer, ForeignKey("tag.id"), primary_key=True) ) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) tags = relationship("Tag", backref="articles", secondary=Article_Tag) def __str__(self): return f"{self.title}" class Tag(Base): __tablename__ = "tag" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(50), nullable=False) def __str__(self): return f"{self.name}" Session = sessionmaker(bind=engine) session = Session() # 獲取tag表的第一行 tag = session.query(Tag).first() # 經過tag.articles獲取對應的article表的內容 print([str(obj) for obj in tag.articles]) # ['article1', 'article2'] # 獲取article表的第一行 article = session.query(Article).first() # 經過article.tags獲取對應的tag表的內容 print([str(obj) for obj in article.tags]) # ['tag1', 'tag2'] # 能夠看到數據所有獲取出來了
咱們知道一旦關聯,那麼刪除父表裏面的數據是沒法刪除的,只能先刪除字表的數據,而後才能刪除關聯的父表數據。若是在orm層面的話,能夠直接刪除父表數據,由於這裏等同於兩步。先將字表中關聯的字段設置爲NULL,而後刪除父表中的數據
咱們將表所有刪除,創建新表。
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) # uid是爲了創建外鍵,咱們要和use表的id進行關聯,因此類型也要和user表的id保持一致 uid = Column(Integer, ForeignKey("user.id")) # 這個是爲了咱們可以經過一張表訪問到另一張表 # 之後User對象即可以經過articles來訪問Articles對象的屬性,Article對象也能夠經過author訪問User對象的屬性 author = relationship("User", backref="articles") Base.metadata.create_all() Session = sessionmaker(bind=engine) session = Session() # 建立幾條記錄 user = User(id=1, username="guido van rossum") article = Article(title="Python之父談論python的將來", uid=1) article.author = user # 而後使用session添加article便可,會自動添加user session.add(article) session.commit()
咱們在數據庫層面刪除一下數據
咱們來試試從orm層面刪除數據
刪除父表的數據,這個過程至關於先將article中的uid設置爲Null,而後刪除父表的數據。可是這樣也有危險,若是不熟悉sqlalchemy的話,會形成不可避免的後果,怎麼辦呢?直接將uid設置爲不可爲空便可便可, 加上nullable=False
orm層面的cascade:
首先咱們知道若是若是數據庫的外鍵設置爲RESTRICT,那麼在orm層面,若是刪除了父表的數據,字表的數據將會被設置爲NULL,若是想避免這一點,那麼只須要將nullable設置爲False便可
可是在SQLAlchemy中,咱們只須要將一個數據添加到session中,提交以後,與其關聯的數據也被自動地添加到數據庫當中了,這是怎麼辦到的呢?實際上是經過relationship的時候,其關鍵字參數cascade設置了這些屬性:
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) uid = Column(Integer, ForeignKey("user.id")) # 其餘不變,這裏顯示的指定了cascade="",那麼便不會再使用默認地save-update了 author = relationship("User", backref="articles", cascade="") Base.metadata.drop_all() Base.metadata.create_all() Session = sessionmaker(bind=engine) session = Session() # 建立幾條記錄 user = User(username="guido van rossum") article = Article(title="Python之父談論python的將來") article.author = user # 而後使用session添加article便可,此時就不會添加user了 session.add(article) session.commit()
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) uid = Column(Integer, ForeignKey("user.id")) # 指定save-update和delete,以逗號分隔便可 author = relationship("User", backref="articles", cascade="save-update,delete") Base.metadata.drop_all() Base.metadata.create_all() Session = sessionmaker(bind=engine) session = Session() # 建立幾條記錄 user = User(username="guido van rossum") article = Article(title="Python之父談論python的將來") article.author = user # 而後使用session添加article便可,此時就不會添加user了 session.add(article) session.commit()
如今添加數據是沒問題的,可是刪除數據呢?若是是默認狀況的話,那麼刪除父表的記錄會將字表對應的記錄設爲空,但若是刪除子表的記錄,是不會影響父表的。可如今我在cascade加上了delete,那麼再刪除子表中的記錄呢?
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) uid = Column(Integer, ForeignKey("user.id")) # 指定save-update和delete,以逗號分隔便可 author = relationship("User", backref="articles", cascade="save-update,delete") Session = sessionmaker(bind=engine) session = Session() article = session.query(Article).first() session.delete(article) session.commit()
此外能夠在模型中定義
class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(String(50), nullable=False) age = Column(Integer) # 就以User爲例,在SQLAlchemy中有一個屬性叫作__mapper_args__ # 是一個字典,咱們能夠指定一個叫作order_by的key,value則爲一個字段 __mapper_args__ = {"order_by": age} # 倒序的話,__mapper_args__ = {"order_by": age.desc()} # 之後再查找的時候直接session.query(table).all()便可,自動按照咱們指定的排序
在一對一或者多對多的時候,若是想要獲取多的這一部分數據的時候,每每經過一個屬性就能夠所有獲取了。好比有一個做者,咱們要獲取這個做者的全部文章,那麼經過user.articles就能夠所有獲取了,可是有時咱們不想獲取全部的數據,好比只獲取這個做者今天發表的文章,那麼這個時候就能夠給relationship中的backref傳遞一個lazy=「dynamic」,之後經過user.articles獲取到的就不是一個列表,而是一個AppendQuery對象了。之後就能夠對這個對象再進行過濾和排序工做。
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) age = Column(Integer) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) uid = Column(Integer, ForeignKey("user.id")) author = relationship("User", backref=backref("articles")) def __str__(self): return f"{self.title}" Base.metadata.drop_all() Base.metadata.create_all() Session = sessionmaker(bind=engine) session = Session() user = User(username="guido", age=63) for i in range(10): article = Article(title=f"title{i}") article.author = user session.add(article) session.commit() user = session.query(User).first() for art in user.articles: print(art) """ title0 title1 title2 title3 title4 title5 title6 title7 title8 title9 """
以上獲取了所有數據,若是指向獲取一部分呢?
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) age = Column(Integer) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) uid = Column(Integer, ForeignKey("user.id")) # 若是獲取部分數據, 只須要加上一個lazy="dynamic"便可 # 注意這裏必定要寫在backref裏面,咱們的目的是爲了經過User模型實例取Article模型實例的屬性,要寫在backref裏面 # 同理backref=backref("articles")和backref="articles"是同樣的,可是之因此要加上裏面的這個backref,是爲了給user提供更好的屬性,好比這裏的懶加載 author = relationship("User", backref=backref("articles", lazy="dynamic")) def __str__(self): return f"{self.title}" Base.metadata.drop_all() Base.metadata.create_all() Session = sessionmaker(bind=engine) session = Session() user = User(username="guido", age=63) for i in range(10): article = Article(title=f"title{i}") article.author = user session.add(article) session.commit() user = session.query(User).first() print(user.articles) """ SELECT article.id AS article_id, article.title AS article_title, article.uid AS article_uid FROM article WHERE %(param_1)s = article.uid """ # 此時打印的是一個sql語句,若是不加lazy="dynamic"的話,打印的是一個列表,準確的說是InstrumentList對象,裏面存儲了不少的Article對象 # 可是如今再也不是了,加上lazy="dynamic"以後,獲得的是一個AppendQuery對象。 # 能夠對比列表和生成器,只有執行的時候纔會產出值 print(type(user.articles)) # <class 'sqlalchemy.orm.dynamic.AppenderQuery'> # 查看一下源碼發現,AppenderQuery這個類繼承在Query這個類,也就是說Query可以使用的,它都能使用 # 而user.articles已是一個Query對象了,至關於session.query(XXX),能夠直接調用filter print([str(obj) for obj in user.articles.filter(Article.id > 5).all()]) # ['title5', 'title6', 'title7', 'title8', 'title9'] # 也能夠動態添加數據 article = Article(title="100") user.articles.append(article) # 這個時候不須要add,只須要commit便可,由於這個user已經在裏面了 # 咱們append以後,只須要commit,那麼append的新的article就提交到數據庫裏面了 session.commit() # 繼續獲取 print([str(obj) for obj in user.articles.filter(Article.id > 5).all()]) # ['title5', 'title6', 'title7', 'title8', 'title9', '100'] """ lazy有如下選擇: 1.select:默認選項,以user.articles爲例,若是沒有訪問user.articles屬性,那麼SQLAlchemy就不會從數據庫中查找文章。一旦訪問,就會查找全部文章,最爲InstrumentList返回 2.dynamic:返回的不是一個InstrumentList,而是一個AppendQuery對象,相似一個生成器,能夠動態添加,查找等等。 主要使用 """
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, Enum, func from sqlalchemy.orm import sessionmaker username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) Session = sessionmaker(bind=engine) session = Session() class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) age = Column(Integer) # 注意:類型爲Enum的時候,必定要指定name gender = Column(Enum("male", "female", "secret", default="male", name="我擦")) Base.metadata.drop_all() Base.metadata.create_all() user1 = User(username="神田空太", age=16, gender="male") user2 = User(username="椎名真白", age=16, gender="female") user3 = User(username="四方茉莉", age=400, gender="female") user4 = User(username="木下秀吉", age=15, gender="secret") user5 = User(username="牧瀨紅莉棲", age=18, gender="female") session.add_all([user1, user2, user3, user4, user5]) session.commit() # group_by:分組,比方說我想查看每一個年齡對應的人數 print(session.query(User.age, func.count(User.id)).group_by(User.age).all()) ''' 輸出結果: [(16, 2), (400, 1), (15, 1), (18, 1) ''' # having:在group_by分組的基礎上進行進一步查詢,比方說我想查看年齡大於16的每個年齡段對應的人數 print(session.query(User.age, func.count(User.id)).group_by(User.age).having(User.age > 16).all()) ''' 輸出結果: [(18, 1), (400, 1)] ''' # 想查看人數大於1的那一組 print(session.query(User.age, func.count(User.id)).group_by(User.age).having(func.count(User.id) > 1).all()) """ 輸出結果: [(16, 2)] """
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) Session = sessionmaker(bind=engine) session = Session() class People(Base): __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) uid = Column(Integer, ForeignKey("people.id")) author = relationship("People", backref=backref("articles", lazy="dynamic")) def __repr__(self): return f"{self.title}" Base.metadata.drop_all() Base.metadata.create_all() people1 = People(username="guido") people2 = People(username="ken") article1 = Article(title="python") article1.author = people1 article2 = Article(title="B") article2.author = people2 article3 = Article(title="go") article3.author = people2 session.add_all([article1, article2, article3]) session.commit()
此時兩張表創建完成,數據已經添加成功
先在數據庫層面上進行查詢,查詢每一個做者發表了多少文章
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, func from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) Session = sessionmaker(bind=engine) session = Session() class People(Base): __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) uid = Column(Integer, ForeignKey("people.id")) author = relationship("People", backref=backref("articles", lazy="dynamic")) def __repr__(self): return f"{self.title}" # 找到全部用戶,按照發表文章的數量進行排序 # 此外還能夠爲字段起一個別名,好比把篩選出來的username改成姓名,能夠調用People.username.label("姓名") # 若是不指定別名,那麼sqlachemy會默認將類名、下劃線、字段名進行組合做爲別名, res = session.query(People.username, func.count(Article.id)).join(Article, People.id == Article.uid).\ group_by(People.id).order_by(func.count(Article.id)) print(res) """ SELECT people.username AS people_username, count(article.id) AS count_1 FROM people JOIN article ON people.id = article.uid GROUP BY people.id ORDER BY count(article.id) """ res = session.query(People.username.label("姓名"), func.count(Article.id).label("次數")).join(Article, People.id == Article.uid).\ group_by(People.id).order_by("次數") print(res) """ SELECT people.username AS "姓名", count(article.id) AS "次數" FROM people JOIN article ON people.id = article.uid GROUP BY people.id ORDER BY "次數" """ print(res.all()) # [('guido', 1), ('ken', 2)]
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, func from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) Session = sessionmaker(bind=engine) session = Session() class Girl(Base): __tablename__ = "girl" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(50), nullable=False) anime = Column(VARCHAR(50), nullable=False) age = Column(Integer, nullable=False) def __repr__(self): return f"{self.name}--{self.anime}--{self.age}" Base.metadata.create_all() girl1 = Girl(name="雨宮優子", anime="悠久之翼", age=16) girl2 = Girl(name="宮村宮子", anime="悠久之翼", age=16) girl3 = Girl(name="古河渚", anime="clannad", age=19) girl4 = Girl(name="牧瀨紅莉棲", anime="命運石之門", age=18) session.add_all([girl1, girl2, girl3, girl4]) session.commit()
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, func from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) Session = sessionmaker(bind=engine) session = Session() class Girl(Base): __tablename__ = "girl" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(50), nullable=False) anime = Column(VARCHAR(50), nullable=False) age = Column(Integer, nullable=False) def __repr__(self): return f"{self.name}--{self.anime}--{self.age}" # 下面要尋找和雨宮優子在同一anime,而且age相同的記錄,固然這裏只有一條 girl = session.query(Girl).filter(Girl.name == "雨宮優子").first() # filter裏面若是沒有指定and_或者or_,那麼默認是and_ expect_girls = session.query(Girl).filter(Girl.anime == girl.anime, Girl.age == girl.age, Girl.name != "雨宮優子").all() print(expect_girls) # [宮村宮子--悠久之翼--16]
這種查找方式,等於先篩選出name="雨宮優子"對應的記錄,而後再從全表搜索出Girl.anime == girl.anime而且Girl.age == girl.age的記錄,寫成sql的話就相似於
也可使用subquery
# 建立一個subquery girl = session.query(Girl).filter(Girl.name == "雨宮優子").subquery() # 這裏的girls.c的c代指的是column,是一個簡寫 expect_girls = session.query(Girl).filter(Girl.anime == girl.c.anime, Girl.age == girl.c.age, Girl.name != "雨宮優子").all() print(expect_girls) # [宮村宮子--悠久之翼--16]
能夠看到事實上沒太大區別,貌似代碼量還多了一丟丟。可是在數據庫裏面,咱們只須要進行一次查詢,效率會高一些
flask-sqlalchemy是flask的一個插件,能夠更加方便咱們去使用。sqlalchemy是能夠獨立於flask而存在的,這個插件是將sqlalchemy集成到flask裏面來。咱們以前使用sqlalchemy的時候,要定義Base,session,各個模型之類的,使用這個插件能夠簡化咱們的工做。
這個插件首先須要安裝,直接pip install flask-sqlalchemy便可。你們注意到沒,flask雖然自己內容較少,可是有不少的第三方插件,擴展性極強。其實flask寫到最後,感受和Django沒太大區別了。提到框架,首先想到的就是flask、Django、tornado,其中tornado是異步的。這裏也主要想說的就是python中的異步框架,自從python3.5引入了async和await關鍵字、能夠定義原生協程以後,python中的異步框架也是層出不窮,可是並無一個怪獸級別的一步框架一統江湖。python中的tornado是一個,還有一個sanic,這是一個仿照flask接口設計的異步框架,只能部署在linux上,聽說能夠達到媲美go語言的性能,具體沒有測試過,但缺點是第三方擴展沒有flask這麼多,質量也良莠不齊。但願python能出現優秀的異步框架,目前的話仍是推薦tornado,由於在python尚未引入原生協程的時候,就有了tornado,當時tornado是本身根據生成器實現協程、手動實現了一套事件循環機制。可是當python引入了原生的協程以後,咱們在定義視圖類的時候,就不須要在使用裝飾器@tornado.gen.coroutine了,直接經過async def來定義函數便可,也不須要yield了,直接await便可,並且底層的事件循環也再也不使用原來的哪一套了,而是直接使用的asyncio。目前異步框架,仍是推薦tornado。
flask-sqlalchemy也是能夠獨立於flask而存在的,或者不以web的方式。
from flask import Flask from flask_sqlalchemy import SQLAlchemy username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 db_uri = f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}" # 這個配置不直接與咱們的SQLAlchemy這個類發生關係,而是要添加到app.config裏面,這一步是少不了的 # 至於這裏的key,做者規定就是這麼寫的 app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = db_uri # 而且還要加上這一段,否則會彈出警告 app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False # 接收一個app,今後db便具備了app的功能,但咱們只用數據庫的功能 db = SQLAlchemy(app) # 創建模型,確定要繼承,那麼繼承誰的,繼承自db.Module,至關於以前的Base。這裏操做簡化了,不須要咱們去建立了 class User(db.Model): __tablename__ = "user" # 能夠看到,以前須要導入的統統不須要導入了,都在db下面。不過本質上調用的仍是sqlalchemy模塊裏的類。 id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String(50), nullable=False) class Article(db.Model): __tablename__ = "article" id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.String(50), nullable=False) uid = db.Column(db.Integer, db.ForeignKey("user.id")) author = db.relationship("User", backref="articles") # 因此咱們發現這和SQLAlchemy框架中的使用方法基本上是一致的,只不過咱們在SQLAlchemy中須要導入的,如今所有能夠經過db來訪問 # 那麼如何映射到數據庫裏面呢?這裏也不須要Base.metadata了,直接使用db便可。 db.create_all() # 下面添加數據 user = User(name="guido") article = Article(title="python之父談python的將來") user.articles.append(article) # 這裏的session也不須要建立了,由於在app中咱們指定了SQLALCHEMY_DATABASE_URI # 會自動根據配置建立session db.session.add(user) ''' 或者 article.author = user db.session.add(article) ''' db.session.commit() # 跟咱們以前使用SQLAlchemy的流程基本一致
那麼問題來了,如何查找數據呢?首先咱們能夠想到db.session.query(User),這毫無疑問是能夠的,可是咱們的模型繼承了db.Model,那麼咱們有更簡單的方法
# 直接使用User.query便可,就等價於db.session.query(User) user = User.query.first() print(user.name) # guido print(user.articles[0].title) # python之父談python的將來 ''' 能夠看到,使用方法和session.query(Model)沒有啥區別 '''
alembic是SQLAlchemy做者所寫的一款用於作ORM與數據庫的遷移和映射的一個框架,相似於git。
首先確定要安裝,pip install alembic
注意這個目錄
進入文件夾裏面,輸入alembic init xxxx
接下來創建模型,這個是獨立於flask的,因此咱們此次仍是使用SQLAlchemy作演示
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import create_engine, func from sqlalchemy import Column, Integer, String username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class People(Base): __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(20), nullable=False) # 不可爲空 age = Column(Integer, nullable=False) def __repr__(self): return f"{self.name}--{self.age}"
下面修改配置文件,alembic.ini
而後修改env.py
接下來生成遷移文件,alembic revision --autogenerate -m "message",這裏的"message"是咱們的註釋信息
下面就要更新數據庫了,將剛纔生成的遷移文件映射到數據庫。至於爲何須要遷移文件,那是由於沒法直接映射orm模型,須要先轉化爲遷移文件,而後才能映射到數據庫當中。
alembic upgrade head,將剛剛生成的遷移文件映射到數據庫當中
若是須要修改表的結構,不須要再drop_all,create_all了,若是裏面有大量數據,不可能清空以後從新建立。那麼在修改以後,直接再次生成遷移文件而後映射到數據庫就能夠了
先來看看數據庫的表
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import create_engine, func from sqlalchemy import Column, Integer, String username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class People(Base): __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(20), nullable=False) age = Column(Integer, nullable=False) serifu = Column(String(100)) # 增長一列 def __repr__(self): return f"{self.name}--{self.age}"
我給模型添加了一個字段serifu,而後從新生成遷移文件,並再次映射
能夠看到,自動增長了一列,而且原來的數據也沒有被破壞。所以也能夠發現,咱們再增長列的時候,不能設置nullable=False,否則添加不進去
class People(Base): __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(20), nullable=False) # 不可爲空 age = Column(Integer, nullable=False) serifu = Column(String(100), nullable=False) # 不添加默認值,設置不能爲空 def __repr__(self): return f"{self.name}--{self.age}"
總結一下,就是五個步驟:
能夠看出,和Django比較相似,若是之後修改了模型的話,那麼重複4和5便可
經常使用命令:
init:建立一個alembic倉庫
revision:建立一個新的版本文件
--autogenerate:自動將當前模型的修改,生成遷移腳本
-m:本次遷移作了哪些修改,用戶能夠指定這個參數,方便回顧
upgrade:將指定版本的遷移文件映射到數據庫中,會執行版本文件中的upgrade函數。若是有多個遷移腳本沒有被映射到數據庫中,那麼會執行多個遷移腳本
[head]:表明最新的遷移腳本的版本號
downgrade:降級,咱們每個遷移文件都有一個版本號,若是想退回之前的版本,直接使用alembic downgrade version_id
heads:展現head指向的腳本文件
history:列出全部的遷移版本及其信息
current:展現當前數據庫的版本號
經典錯誤:
FAILED:Target database is not up to date。緣由:主要是heads和current不相同。current落後於heads的版本。解決辦法:將current移動到head上,alembic upgrade head
FAILED:can’t locate revision identified by 「78ds75ds7s」。緣由:數據庫中村的版本號不在遷移腳本文件中。解決辦法:刪除數據庫中alembic_version表的數據,而後從新執行alembic upgrade head
執行upgrade head 時報某個表已經存在的錯誤。解決辦法:1.刪除version中全部的遷移文件的代碼,修改遷移腳本中建立表的代碼
flask-script的做用是能夠經過命令行的方式來操做flask。例如經過命令來跑一個開發版本的服務器,設置數據庫,定時任務等等。要使用的話,首先要安裝,pip install flask-script
而後要作什麼呢?
from flask_script import Manager from app import app # 傳入app。生成manager manager = Manager(app) # 加上一個manager.command,使其成爲命令行 @manager.command def hello(): print("你好啊") # 也能夠添加參數,而且此時manager.command也能夠不要了 @manager.option("--name", dest="username") @manager.option("--age", dest="age") def foo(username, age): # 相似於python裏的optparse,能夠見個人python經常使用模塊,裏面有介紹 return f"name={username}, age={age}" if __name__ == '__main__': manager.run() # 這裏是manager.run,不是app.run
start.py
from flask import Flask app = Flask(__name__) username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 app.config["SQLALCHEMY_DATABASE_URI"] = f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}" @app.route('/') def hello_world(): return 'Hello World!' if __name__ == '__main__': app.run()
model.py
from app import app from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy(app) # 話說回來,這個db.Model就至關於以前的Base # 咱們將env裏面的target_metadata = None,也能夠換成db.Model.metadata class User(db.Model): __tablename__ = "user" id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.VARCHAR(100)) age = db.Column(db.Integer) class Score(db.Model): __tablename__ = "score" id = db.Column(db.Integer, primary_key=True, autoincrement=True) math = db.Column(db.Integer) english = db.Column(db.Integer) history = db.Column(db.Integer)
manage.py
from flask_script import Manager from app import app # 從模型裏面導入User和Score模型,以及db from model import User, Score, db # 傳入app。生成manager manager = Manager(app) # 建立表 @ manager.command def create_table(): db.create_all() # 往user表裏面添加數據 @manager.option("--n", dest="name") @manager.option("--a", dest="age") def add_user(name, age): user = User() user.name = name user.age = age db.session.add(user) db.session.commit() # 往score表裏面添加數據 @manager.option("--m", dest="math") @manager.option("--e", dest="english") @manager.option("--h", dest="history") def add_score(math, english, history): score = Score() score.math = math score.english = english score.history = history db.session.add(score) db.session.commit() if __name__ == '__main__': manager.run() # 這裏是manager.run,不是app.run
生成表
給user表添加數據
給score表添加數據
並且咱們還能夠模擬數據庫遷移,映射等等。Django的manage.py不就是這麼作的嗎?python manage.py makemigrations遷移,而後再python manage.py migrate映射。
而這種方式的實現,flask也幫咱們封裝好了
在實際的數據庫開發中,常常會出現數據表修改的行爲。通常咱們不會手動修改數據庫,而是去修改orm模型,而後再把模型映射到數據庫中。這個時候若是能有一個工具專門作這件事情就很是好了,而flask-migrate就是用來幹這個的。flask-migrate是基於alembic的一個封裝,並集成到flask當中,而全部的操做都是alembic作的,它能跟蹤模型的變化,並將模型映射到數據庫中。
顯然也要安裝,pip install flask-migrate,因此flask有着豐富的第三方插件,能夠本身定製。因此寫到最後,真的和Django沒啥區別了。
model.py
from flask_script import Manager from app import app # 從模型裏面導入User和Score模型,以及db from model import User, Score, db from flask_migrate import Migrate, MigrateCommand # 傳入app。生成manager manager = Manager(app) # 傳入app和db,將app和db綁定在一塊兒 migrate = Migrate(app, db) # 把MigrateCommand命令添加到manager中 manager.add_command("db", MigrateCommand) if __name__ == '__main__': # 此時就能夠了,你們可能看到咱們這裏導入了User和Score模型,可是沒有使用 # 其實不是,生成數據庫的表,是在命令行中操做的 # 爲了在映射的時候可以找到這兩個模型,因此要導入,只不過找模型咱們不須要作,flask-migrate會自動幫咱們處理 # 咱們只負責導入就能夠了 manager.run() # 這裏是manager.run,不是app.run
執行python manage.py db init。這裏的db就是咱們manager.add_command("db", MigrateCommand)的db,咱們也能夠起其餘的名字
此時裏面的文件,咱們也不須要手動去改了,都幫咱們弄好了
python manage.py db migrate,生成遷移文件
python manage.py db upgrade,將遷移文件映射到數據庫中
總結以下: 介紹:由於採用db.create_all在後期修改字段的時候,不會自動的映射到數據庫中,必須刪除表,而後從新運行db.craete_all,或者先db.drop_all,纔會從新映射,這樣不符合咱們的需求。所以flask-migrate就是爲了解決這個問題,它能夠在每次修改模型後,能夠將修改的東西映射到數據庫中。 使用flask_migrate必須藉助flask_scripts,這個包的MigrateCommand中包含了全部和數據庫相關的命令。 flask_migrate相關的命令: python manage.py db init:初始化一個遷移腳本的環境,只須要執行一次。 python manage.py db migrate:將模型生成遷移文件,只要模型更改了,就須要執行一遍這個命令。 python manage.py db upgrade:將遷移文件真正的映射到數據庫中。每次運行了migrate命令後,就記得要運行這個命令。 注意點:須要將你想要映射到數據庫中的模型,都要導入到manage.py文件中,若是沒有導入進去,就不會映射到數據庫中。
咱們來使用web的方式,添加數據吧
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/add" method="post"> <table> <tr> <td>用戶名:</td> <td><input name="username" type="text"/></td> </tr> <tr> <td>年齡:</td> <td><input name="age" type="text"/></td> </tr> <tr> <td>提交:</td> <td><input type="submit"/></td> </tr> </table> </form> </body> </html>
咱們將代碼的結構從新修改一下
config.py:存放配置
username = "postgres" # 用戶名 password = "zgghyys123" # 密碼 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 數據庫種類 driver = "psycopg2" # 驅動 database = "postgres" # 鏈接到哪一個數據庫 SQLALCHEMY_DATABASE_URI = f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}" SQLALCHEMY_TRACK_MODIFICATIONS = False
exts.py
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy()
model.py
from exts import db class User(db.Model): __tablename__ = "user" id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.VARCHAR(100)) age = db.Column(db.Integer) class Score(db.Model): __tablename__ = "score" id = db.Column(db.Integer, primary_key=True, autoincrement=True) math = db.Column(db.Integer) english = db.Column(db.Integer) history = db.Column(db.Integer)
看到這裏可能發現了,這裏的db並無傳入app啊,那麼它是如何找到的呢?別急
manage.py
from flask_script import Manager from app import app from model import User, Score from exts import db from flask_migrate import Migrate, MigrateCommand # 傳入app。生成manager manager = Manager(app) # 傳入app和db,將app和db綁定在一塊兒 migrate = Migrate(app, db) # 把MigrateCommand命令添加到manager中 manager.add_command("db", MigrateCommand) if __name__ == '__main__': manager.run()
app.py
from flask import Flask, request, render_template from model import User from exts import db import config app = Flask(__name__) # 導入配置 app.config.from_object(config) # 經過db.init_app(app)會自動地將app裏面的信息綁定到db裏面去 db.init_app(app) @app.route('/') def hello_world(): return 'Hello World!' @app.route("/add", methods=["GET", "POST"]) def add(): if request.method == 'GET': return render_template("add.html") else: name = request.form.get("username", None) age = request.form.get("age", None) user = User() user.name = name user.age = age db.session.add(user) db.session.commit() return f"數據添加成功" if __name__ == '__main__': app.run()
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>中國工商銀行首頁</title> </head> <body> <h1>歡迎來到中國工商銀行</h1> <ul> <li><a href="{{ url_for('register') }}">當即註冊</a></li> </ul> </body> </html>
register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>中國銀行註冊頁面</title> </head> <body> <form action="/register" method="post"> <table> <tbody> <tr> <td>郵箱:</td> <td><input type="email" name="email"></td> </tr> <tr> <td>用戶名:</td> <td><input type="text" name="username"></td> </tr> <tr> <td>密碼:</td> <td><input type="password" name="password"></td> </tr> <tr> <td>重複密碼:</td> <td><input type="password" name="repeat_password"></td> </tr> <tr> <td>餘額:</td> <td><input type="text", name="deposit"></td> </tr> <tr> <td></td> <td><input type="submit" value="當即註冊"></td> </tr> </tbody> </table> </form> </body> </html>
exts.py
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy()
model.py
from exts import db class User(db.Model): __tablename__ = "user" id = db.Column(db.Integer, primary_key=True, autoincrement=True) email = db.Column(db.String(50), nullable=False) username = db.Column(db.String(50), nullable=False) password = db.Column(db.String(50), nullable=False) deposit = db.Column(db.Float(50), default=10)
manage.py
from flask_script import Manager from app import app from model import User from exts import db from flask_migrate import Migrate, MigrateCommand # 傳入app。生成manager manager = Manager(app) # 傳入app和db,將app和db綁定在一塊兒 migrate = Migrate(app, db) # 把MigrateCommand命令添加到manager中 manager.add_command("db", MigrateCommand) if __name__ == '__main__': manager.run()
forms.py
from wtforms import Form, StringField, FloatField from wtforms.validators import Length, EqualTo, Email, InputRequired class RegisterForm(Form): email = StringField(validators=[Email()]) username = StringField(validators=[Length(6, 20)]) password = StringField(validators=[Length(6, 20)]) repeat_password = StringField(validators=[EqualTo("password")]) deposit = FloatField(validators=[InputRequired()])
app.py
from flask import Flask, render_template, request, views from forms import RegisterForm from exts import db from model import User import config app = Flask(__name__) app.config.from_object(config) db.init_app(app) # 這個和db = SQLAlchemy(app)效果是同樣的 @app.route('/') def index(): return render_template("index.html") class RegisterView(views.MethodView): def get(self): return render_template("register.html") def post(self): form = RegisterForm(request.form) if form.validate(): email = form.email.data username = form.username.data password = form.password.data deposit = form.deposit.data user = User(email=email, username=username, password=password, deposit=deposit) db.session.add(user) db.session.commit() return "註冊成功" else: return f"註冊失敗,{form.errors}" app.add_url_rule("/register", view_func=RegisterView.as_view("register")) if __name__ == '__main__': app.run()
將以前的表所有刪除,而後執行python manage.py db init,而後執行python manage.py db migrate 而後執行python manage.py db upgrade,而後會發現數據庫多了一張user表
訪問localhost:5000
發現數據已經被添加到數據庫裏面了
首頁,index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>中國工商銀行首頁</title> </head> <body> <h1>歡迎來到中國工商銀行</h1> <ul> <li><a href="{{ url_for('register') }}">當即註冊</a></li> <li><a href="{{ url_for('login') }}">當即登陸</a></li> <li><a href="{{ url_for('transfer') }}">當即轉帳</a></li> </ul> </body> </html>
註冊頁面,register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>中國銀行註冊頁面</title> </head> <body> <form action="/register" method="post"> <table> <tbody> <tr> <td>郵箱:</td> <td><input type="email" name="email"></td> </tr> <tr> <td>用戶名:</td> <td><input type="text" name="username"></td> </tr> <tr> <td>密碼:</td> <td><input type="password" name="password"></td> </tr> <tr> <td>重複密碼:</td> <td><input type="password" name="repeat_password"></td> </tr> <tr> <td>餘額:</td> <td><input type="text", name="deposit"></td> </tr> <tr> <td></td> <td><input type="submit" value="當即註冊"></td> </tr> </tbody> </table> </form> </body> </html>
登陸頁面,login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>中國工商銀行登陸頁面</title> </head> <body> <form action="/login" method="post"> <table> <tbody> <tr> <td>郵箱:</td> <td><input name="email" type="email"></td> </tr> <tr> <td>密碼:</td> <td><input name="password" type="password"></td> </tr> <tr> <td></td> <td><input type="submit" value="當即登陸"></td> </tr> </tbody> </table> </form> </body> </html>
轉帳頁面,transfer.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/transfer" method="post"> <table> <tbody> <tr> <td>轉到郵箱:</td> <td><input type="email" name="email"></td> </tr> <tr> <td>轉帳金額:</td> <td><input type="text" name="money"></td> </tr> <tr> <td></td> <td><input type="submit" value="當即轉帳"></td> </tr> </tbody> </table> </form> </body> </html>
py文件只有forms.py和app.py發生了變化,其餘的沒變
forms.py
from wtforms import Form, StringField, FloatField from wtforms.validators import Length, EqualTo, Email, InputRequired, NumberRange from model import User class RegisterForm(Form): email = StringField(validators=[Email()]) username = StringField(validators=[Length(6, 20)]) password = StringField(validators=[Length(6, 20)]) repeat_password = StringField(validators=[EqualTo("password")]) deposit = FloatField(validators=[InputRequired()]) class LoginForm(Form): email = StringField(validators=[Email()]) password = StringField(validators=[Length(6, 20)]) class TransferForm(Form): email = StringField(validators=[Email()]) money = FloatField(validators=[NumberRange(min=1, max=10000)])
app.py
from flask import Flask, render_template, request, views, session, redirect, url_for from forms import RegisterForm, LoginForm, TransferForm from exts import db from model import User import secrets app = Flask(__name__) app.config["SECRET_KEY"] = secrets.token_bytes() app.config.from_object(config) db.init_app(app) # 這個和db = SQLAlchemy(app)效果是同樣的 @app.route('/') def index(): return render_template("index.html") # 註冊 class RegisterView(views.MethodView): def get(self): return render_template("register.html") def post(self): form = RegisterForm(request.form) if form.validate(): email = form.email.data username = form.username.data password = form.password.data deposit = form.deposit.data user = User(email=email, username=username, password=password, deposit=deposit) db.session.add(user) db.session.commit() return "註冊成功" else: return f"註冊失敗,{form.errors}" app.add_url_rule("/register", view_func=RegisterView.as_view("register")) # 登陸 class LoginView(views.MethodView): def get(self): return render_template("login.html") def post(self): form = LoginForm(request.form) if form.validate(): email = form.email.data password = form.password.data user = User.query.filter(User.email == email, User.password == password).first() if user: session["session_id"] = user.id return "登陸成功" else: return "郵箱或密碼錯誤" else: return f"{form.errors}" app.add_url_rule("/login", view_func=LoginView.as_view("login")) # 轉帳 class TransferView(views.MethodView): def get(self): # 只有登陸了才能轉帳,不然讓其滾回登陸頁面 if session.get("session_id"): return render_template("transfer.html") else: return redirect(url_for("login")) def post(self): form = TransferForm(request.form) if form.validate(): email = form.email.data money = form.money.data user = User.query.filter_by(email=email).first() if user: session_id = session.get("session_id") myself = User.query.get(session_id) if myself.deposit >= money: user.deposit += money myself.deposit -= money db.session.commit() return f"轉帳成功,您向{user.email}轉了{money}" else: return "您的資金不足,沒法完成當前轉帳" else: return "該用戶不存在" else: return "數據填寫不正確" app.add_url_rule("/transfer", view_func=TransferView.as_view("transfer")) if __name__ == '__main__': app.run()
下面進行轉帳
咱們接下來看看數據庫裏面的金額
此時繼續轉帳
顯示資金不夠了