Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
SQLAlchemy è il toolkit Python ORM e database più utilizzato.
SQLAlchemy 2.1 include un dialetto integrato per il mssql-python driver che puoi usare per lavorare con SQLAlchemy ORM e Core con Microsoft SQL e database SQL di Azure.
Prerequisiti
- Python 3.11 o versione successiva. SQLAlchemy 2.1 ha eliminato il supporto per Python 3.10 e precedenti.
- I pacchetti
mssql-pythonesqlalchemy(versione 2.1 o successive).
Gli esempi in questo articolo utilizzano il database di esempio AdventureWorksLT . Se non hai installato AdventureWorksLT, consulta i database di esempio di AdventureWorks.
Installa SQLAlchemy e mssql-python
Usa la dipendenza opzionale di mssql-pythonSQLAlchemy per installare entrambi i pacchetti:
pip install "sqlalchemy[mssql-python]>=2.1"
SQLAlchemy definisce l'extra mssql-python con mssql-python>=1.9.0. È supportato anche il formato esistente in pacchetti separati:
pip install "sqlalchemy>=2.1" mssql-python
Verificare la versione installata:
import sqlalchemy
print(sqlalchemy.__version__) # Should show 2.1.0 or later
URL di connessione
Il dialetto mssql-python usa mssql+mssqlpython come schema URL. Il formato generale è:
mssql+mssqlpython://<username>:<password>@<host>:<port>/<database>
Autenticazione SQL
Per l'autenticazione SQL, includere nome utente e password nell'URL di connessione:
from sqlalchemy import create_engine
# Replace <password> with your actual password. Avoid using the sa account in production.
engine = create_engine(
"mssql+mssqlpython://dbuser:<password>@localhost:1433/<database>"
)
Autenticazione Microsoft Entra
Per l'autenticazione con Microsoft Entra, usa un nome utente vuoto e il parametro di query authentication:
from sqlalchemy import create_engine
engine = create_engine(
"mssql+mssqlpython://@<server>.database.windows.net/<database>"
"?authentication=ActiveDirectoryDefault&encrypt=yes"
)
Note
ActiveDirectoryDefault usa DefaultAzureCredential, che prova più fornitori di credenziali in sequenza. La prima connessione può essere lenta perché l'SDK percorre la catena finché non trova un fornitore funzionante. In produzione, se sai quale tipo di credenziale utilizza il tuo ambiente, specificalo direttamente (ad esempio, ActiveDirectoryMSI per l'identità gestita) per evitare il chain walk. Per altre informazioni, vedere Autenticazione di Microsoft Entra.
Creare URL a livello di codice
Da usare sqlalchemy.engine.URL.create per evitare la codifica manuale degli URL:
from sqlalchemy.engine import URL
url = URL.create(
"mssql+mssqlpython",
username="dbuser",
password="<password>",
host="localhost",
port=1433,
database="<database>",
)
engine = create_engine(url)
Definire modelli ORM
Usa la mappatura dichiarativa di SQLAlchemy per definire modelli che si mappano alle tabelle SQL di Microsoft.
from datetime import datetime
from decimal import Decimal
from sqlalchemy import Identity, String, Numeric, Integer, DateTime, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class Product(Base):
__tablename__ = "Product"
__table_args__ = {"schema": "SalesLT"}
product_id: Mapped[int] = mapped_column(
"ProductID", Integer, Identity(), primary_key=True
)
name: Mapped[str] = mapped_column("Name", String(50))
product_number: Mapped[str] = mapped_column("ProductNumber", String(25))
color: Mapped[str | None] = mapped_column("Color", String(15))
list_price: Mapped[Decimal] = mapped_column("ListPrice", Numeric(19, 4))
standard_cost: Mapped[Decimal] = mapped_column("StandardCost", Numeric(19, 4))
size: Mapped[str | None] = mapped_column("Size", String(5))
product_category_id: Mapped[int | None] = mapped_column("ProductCategoryID", Integer)
sell_start_date: Mapped[datetime] = mapped_column("SellStartDate", DateTime)
modified_date: Mapped[datetime] = mapped_column(
"ModifiedDate", DateTime, server_default=func.getdate()
)
Tip
Microsoft SQL utilizza IDENTITY l'incremento automatico delle colonne.
SQLAlchemy mappa automaticamente questo parametro per le colonne chiave primarie intere. L'esplicito Identity() mostrato sopra è opzionale, a meno che tu non debba controllare i valori di inizio e incremento.
Operazioni CRUD
I seguenti esempi mostrano come inserire, interrogare, aggiornare ed eliminare righe utilizzando la sessione ORM. Ogni esempio riutilizza new_id, il valore ProductID restituito quando viene inserita una riga. Per eseguire tutte e quattro le operazioni insieme, vedi l'esempio completo.
Creare una sessione
Crea una sessione per eseguire operazioni all'interno di una transazione:
from sqlalchemy.orm import Session
with Session(engine) as session:
# Use session for queries and modifications
pass
Per applicazioni che creano molte sessioni, si utilizza sessionmaker:
from sqlalchemy.orm import sessionmaker
SessionLocal = sessionmaker(bind=engine)
Inserire righe
Aggiungi un nuovo prodotto, esegui il commit della sessione e acquisisci il tag ProductID generato per i seguenti esempi:
from datetime import datetime
with Session(engine) as session:
product = Product(
name="Classic Road Bike",
product_number="BK-C001",
color="Red",
list_price=Decimal("1299.99"),
standard_cost=Decimal("749.99"),
sell_start_date=datetime(2026, 1, 1),
product_category_id=6,
)
session.add(product)
session.commit()
new_id = product.product_id
print(f"Inserted ProductID: {new_id}")
Note
In SalesLT.Product, sia Name che ProductNumber hanno vincoli unici. Se esegui questo insert più di una volta, cambia questi valori o elimina prima la riga precedente.
L'esempio completo elimina la riga che crea, così da poterlo eseguire ripetutamente.
Righe di query
Recupera una singola riga tramite chiave primaria, oppure usa select() per query filtrate:
from sqlalchemy import select
with Session(engine) as session:
# Single row by primary key (new_id is from the insert example)
product = session.get(Product, new_id)
if product:
print(f"{product.name}: ${product.list_price}")
# Filtered query
stmt = select(Product).where(Product.list_price < 500).order_by(Product.name)
products = session.scalars(stmt).all()
for p in products:
print(f"{p.name}: ${p.list_price}")
Aggiornamento delle righe
Modifica un campo su una riga esistente e effettua un commit:
with Session(engine) as session:
product = session.get(Product, new_id)
if product:
product.list_price = Decimal("1349.99")
session.commit()
Elimina righe
Rimuovi una riga e fai il commit di:
with Session(engine) as session:
product = session.get(Product, new_id)
if product:
session.delete(product)
session.commit()
Esempio completo
Le sezioni precedenti mostravano ogni pezzo separatamente. Questa sezione li combina in un unico script autonomo che puoi copiare, eseguire e ripetere.
Creare un file denominato crud.py e aggiungere il codice seguente. Sostituisci i dettagli create_engine di connessione con i tuoi (vedi URL di connessione):
from datetime import datetime
from decimal import Decimal
from sqlalchemy import create_engine, Identity, String, Numeric, Integer, DateTime, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
# Replace <password> and <database> with your connection details.
engine = create_engine(
"mssql+mssqlpython://dbuser:<password>@localhost:1433/<database>"
)
class Base(DeclarativeBase):
pass
class Product(Base):
__tablename__ = "Product"
__table_args__ = {"schema": "SalesLT"}
product_id: Mapped[int] = mapped_column(
"ProductID", Integer, Identity(), primary_key=True
)
name: Mapped[str] = mapped_column("Name", String(50))
product_number: Mapped[str] = mapped_column("ProductNumber", String(25))
color: Mapped[str | None] = mapped_column("Color", String(15))
list_price: Mapped[Decimal] = mapped_column("ListPrice", Numeric(19, 4))
standard_cost: Mapped[Decimal] = mapped_column("StandardCost", Numeric(19, 4))
size: Mapped[str | None] = mapped_column("Size", String(5))
product_category_id: Mapped[int | None] = mapped_column("ProductCategoryID", Integer)
sell_start_date: Mapped[datetime] = mapped_column("SellStartDate", DateTime)
modified_date: Mapped[datetime] = mapped_column(
"ModifiedDate", DateTime, server_default=func.getdate()
)
with Session(engine) as session:
# Create
product = Product(
name="Classic Road Bike",
product_number="BK-C001",
color="Red",
list_price=Decimal("1299.99"),
standard_cost=Decimal("749.99"),
sell_start_date=datetime(2026, 1, 1),
product_category_id=6,
)
session.add(product)
session.commit()
new_id = product.product_id
print(f"Inserted ProductID: {new_id}")
# Read
product = session.get(Product, new_id)
print(f"Read: {product.name} costs ${product.list_price}")
# Update
product.list_price = Decimal("1349.99")
session.commit()
print(f"Updated price to ${product.list_price}")
# Delete
session.delete(product)
session.commit()
print(f"Deleted ProductID: {new_id}")
Eseguire lo script:
python crud.py
Si vedono risultati simili ai seguenti:
Inserted ProductID: 1019
Read: Classic Road Bike costs $1299.9900
Updated price to $1349.9900
Deleted ProductID: 1019
Lo script elimina la riga che crea, così non colpisce i vincoli unici su Name e ProductNumber quando lo esegui di nuovo. Ogni esecuzione inserisce una nuova riga, quindi il valore di ProductID aumenta ogni volta.
Query principali
SQLAlchemy Core fornisce un'API di espressioni SQL di livello inferiore. Puoi usare Core con lo stesso motore e definizioni di tabelle, incluse le classi mappate con ORM.
from sqlalchemy import text
with engine.connect() as conn:
result = conn.execute(text("SELECT @@VERSION"))
print(result.scalar())
Usa costrutti a livello di tabella per la generazione di SQL con controllo dei tipi:
from sqlalchemy import insert, select, update, delete
with engine.connect() as conn:
# Insert
conn.execute(
insert(Product).values(
name="Touring Bike",
product_number="BK-T002",
list_price=Decimal("999.99"),
standard_cost=Decimal("575.00"),
sell_start_date=datetime(2026, 1, 1)
)
)
conn.commit()
# Select
stmt = select(
Product.name.label("name"),
Product.list_price.label("list_price"),
).where(Product.list_price > 100)
for row in conn.execute(stmt):
print(row.name, row.list_price)
# Delete the inserted row so this example can run again
conn.execute(delete(Product).where(Product.product_number == "BK-T002"))
conn.commit()
Note
Quando selezioni colonne mappate singole il cui nome del database differisce dal nome dell'attributo (ad esempio, Product.name mappa alla Name colonna), le righe Core sono codificate dal nome della colonna del database. Aggiungi .label("name") per accedere al valore come row.name invece di row.Name.
Pool di connessioni
SQLAlchemy gestisce di default un pool di connessioni. Regola le impostazioni del pool per il tuo carico di lavoro:
engine = create_engine(
"mssql+mssqlpython://dbuser:<password>@localhost/<database>",
pool_size=10,
max_overflow=20,
pool_timeout=30,
pool_recycle=3600,
)
| Parametro | Description |
|---|---|
pool_size |
Numero di connessioni da mantenere aperte (predefinito: 5). |
max_overflow |
Connessioni consentite oltre pool_size (predefinito: 10). |
pool_timeout |
Pochi secondi per aspettare una connessione prima di generare un errore (predefinito: 30). |
pool_recycle |
Pochi secondi dopo di che una connessione viene riciclata (predefinito: -1, disabilitato). Imposta questo valore se il tuo database chiude le connessioni inattive. |
Utilizzo con framework web
SQLAlchemy è comunemente utilizzato come livello database per Flask e FastAPI. Il dialetto mssql-python funziona con qualsiasi framework che supporti SQLAlchemy.
I seguenti estratti mostrano il modello consigliato sessione-per-richiesta per ciascun framework. Sono frammenti illustrativi che assumono il engine modello e Product dalle sezioni precedenti, non app complete. Per applicazioni complete ed eseguibili, consulta gli articoli sull'integrazione FastAPI e sull'integrazione Flask .
Esempio di FastAPI
Usa una dipendenza dal generatore per fornire una sessione per richiesta:
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy import create_engine
engine = create_engine("mssql+mssqlpython://dbuser:<password>@<server>/<database>")
SessionLocal = sessionmaker(bind=engine)
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/products/{product_id}")
def read_product(product_id: int, db: Session = Depends(get_db)):
product = db.get(Product, product_id)
if not product:
raise HTTPException(status_code=404, detail="Product not found")
return {"name": product.name, "price": float(product.list_price)}
Esempio di Flask
Usa un gestore di contesto per definire la sessione in base alla richiesta:
from flask import Flask, jsonify
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy import create_engine
engine = create_engine("mssql+mssqlpython://dbuser:<password>@<server>/<database>")
SessionLocal = sessionmaker(bind=engine)
app = Flask(__name__)
@app.route("/products/<int:product_id>")
def read_product(product_id):
with SessionLocal() as session:
product = session.get(Product, product_id)
if not product:
return jsonify({"error": "Not found"}), 404
return jsonify({"name": product.name, "price": float(product.list_price)})
Migrazioni alambiche
Alembic gestisce le migrazioni degli schemi per i progetti SQLAlchemy e funziona con il dialetto mssql-python. La funzione autogenerate di Alembic confronta i tuoi modelli con il database live, quindi qualche passaggio in più impedisce che proponga modifiche alle tabelle che non gestisci.
Istituire Alembic
Installa Alembic e inizializza una directory di migrazione:
pip install alembic
alembic init migrations
In alembic.ini, imposta l'URL di connessione:
sqlalchemy.url = mssql+mssqlpython://dbuser:<password>@localhost/<database>
Indirizza Alembic ai tuoi modelli
Autogenerate ha bisogno dei metadati dei tuoi modelli. Inserisci i modelli gestiti da Alembic in un modulo importabile, come models.py. Poiché autogenerate propone di eliminare qualsiasi colonna omessa da un modello, definisci un modello che possiede completamente la sua tabella invece di riutilizzare il modello semplificato Product di questo articolo precedente:
# models.py
from datetime import datetime
from sqlalchemy import Identity, String, Integer, DateTime, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class ProductReview(Base):
__tablename__ = "ProductReview"
__table_args__ = {"schema": "SalesLT"}
review_id: Mapped[int] = mapped_column("ReviewID", Integer, Identity(), primary_key=True)
product_id: Mapped[int] = mapped_column("ProductID", Integer)
reviewer_name: Mapped[str] = mapped_column("ReviewerName", String(50))
rating: Mapped[int] = mapped_column("Rating", Integer)
comments: Mapped[str | None] = mapped_column("Comments", String(500))
modified_date: Mapped[datetime] = mapped_column("ModifiedDate", DateTime, server_default=func.getdate())
Caution
Per impostazione predefinita, autogenerate considera rimossa ogni tabella nel database che non è in target_metadata e genera drop_table per essa. Contro un database esistente come AdventureWorksLT, quell'azione può far cadere decine di tabelle. Aggiungi un include_name filtro così Alembic gestisce solo le tabelle definite dai tuoi modelli, e rivedi sempre lo script generato prima di applicarlo.
In migrations/env.py, sostituisci target_metadata = None con il seguente codice. Importa i tuoi modelli e limita la generazione automatica agli schemi e alle tabelle che definiscono:
from models import Base
target_metadata = Base.metadata
# Limit autogenerate to the tables your models define.
managed_schemas = {table.schema for table in target_metadata.tables.values()}
managed_tables = {table.name for table in target_metadata.tables.values()}
def include_name(name, type_, parent_names):
if type_ == "schema":
return name in managed_schemas
if type_ == "table":
return name in managed_tables
return True
Passa include_name e include_schemas=True a context.configure sia in run_migrations_offline sia in run_migrations_online. L'impostazione include_schemas=True permette ad Alembic di vedere le tabelle in schemi non predefiniti come SalesLT:
context.configure(
connection=connection,
target_metadata=target_metadata,
include_name=include_name,
include_schemas=True,
)
Genera e applica una migrazione
Genera una migrazione dai tuoi modelli:
alembic revision --autogenerate -m "add product review table"
Alembic rileva la nuova tabella e scrive uno script di migrazione:
INFO [alembic.autogenerate.compare.tables] Detected added table 'SalesLT.ProductReview'
Generating .../versions/xxxx_add_product_review_table.py ... done
Il comando generato upgrade() crea la tabella, e downgrade() la rimuove:
def upgrade() -> None:
op.create_table(
"ProductReview",
sa.Column("ReviewID", sa.Integer(), sa.Identity(always=False), nullable=False),
sa.Column("ProductID", sa.Integer(), nullable=False),
sa.Column("ReviewerName", sa.String(length=50), nullable=False),
sa.Column("Rating", sa.Integer(), nullable=False),
sa.Column("Comments", sa.String(length=500), nullable=True),
sa.Column("ModifiedDate", sa.DateTime(), server_default=sa.text("getdate()"), nullable=False),
sa.PrimaryKeyConstraint("ReviewID"),
schema="SalesLT",
)
def downgrade() -> None:
op.drop_table("ProductReview", schema="SalesLT")
Rivedi lo script e poi applica tutte le migrazioni in sospeso:
alembic upgrade head
Differenze rispetto al dialetto pyodbc
Se stai migrando da mssql+pyodbc, il dialetto mssql-python è simile perché entrambi i driver si basano sullo stesso framework ODBC. Differenze principali:
| Topic | mssql+pyodbc |
mssql+mssqlpython |
|---|---|---|
| Installazione del driver ODBC | Richiede un driver ODBC separato (ad esempio, ODBC Driver 18 per SQL Server). | Installato automaticamente come dipendenza da pacchetto. Non è necessaria l'installazione separata di driver ODBC. |
| URL connessione | mssql+pyodbc://user:pass@host/db?driver=ODBC+Driver+18+for+SQL+Server |
mssql+mssqlpython://user:pass@host/db |
fast_executemany |
Supportato tramite create_engine(..., fast_executemany=True). |
Non applicabile. Il driver gestisce internamente le prestazioni dell'elaborazione batch. |
| Disponibilità | Stabile, inclusa in SQLAlchemy fin dalla versione 1.x. | Stabile, incluso in SQLAlchemy 2.1 o successivo. |
Considerazioni sull'aggiornamento
SQLAlchemy 2.1 include cambiamenti comportamentali che potrebbero influenzare l'aggiornamento delle applicazioni rispetto a versioni precedenti:
- SQLAlchemy 2.1 richiede Python 3.11 o successivo.
- Aggiorna le applicazioni SQLAlchemy 1.x a SQLAlchemy 2.0 prima di passare alla 2.1.
- Testa le applicazioni esistenti di SQLAlchemy 2.0 rispetto ai cambiamenti comportamentali descritti in What's New in SQLAlchemy 2.1?
Gli esempi in questo articolo utilizzano l'API sincrona di SQLAlchemy. Le applicazioni che utilizzano il supporto asyncio di SQLAlchemy devono installare l'extra asyncio perché SQLAlchemy 2.1 non installa più greenlet per impostazione predefinita.
Troubleshooting
"Nessun modulo chiamato 'sqlalchemy.dialects.mssql.mssqlpython'"
Questo errore significa che la versione installata di SQLAlchemy non include il mssql-python dialetto. Installa SQLAlchemy 2.1 o versione successiva insieme alla relativa mssql-python dipendenza:
pip install --upgrade "sqlalchemy[mssql-python]>=2.1"
Errori di connessione
Se create_engine ha successo ma le query falliscono, verifica che i parametri di connessione funzionino direttamente con mssql-python:
import mssql_python
conn = mssql_python.connect(
"Server=localhost;Database=<database>;UID=dbuser;PWD=<password>;Encrypt=yes"
)
cursor = conn.cursor()
cursor.execute("SELECT 1")
print(cursor.fetchone())
conn.close()
Se la connessione diretta funziona ma SQLAlchemy no, controlla eventuali problemi di codifica URL in caratteri speciali all'interno della password o del nome del server.