MetaDapper: Η χαρτογράφηση δεδομένων και η μετατροπή γίνονται εύκολα με τα σωστά εργαλεία
Επιστήμη Δεδομένων Και Βάσεις Δεδομένων
Τα καλά γλωσσικά πλαίσια προγραμματισμού διευκολύνουν την ταχύτερη παραγωγή ποιοτικών προϊόντων. Τα μεγάλα πλαίσια κάνουν ακόμη και όλη την εμπειρία ανάπτυξης ευχάριστη. Το FastAPI είναι ένα νέο πλαίσιο ιστού Python που είναι ισχυρό και ευχάριστο στη χρήση. Οι ακόλουθες δυνατότητες κάνουν το FastAPI να δοκιμάζεται:
Για να εξερευνήσετε τις μεγάλες ιδέες πίσω από το FastAPI, ας δημιουργήσουμε μια εφαρμογή TODO, η οποία δημιουργεί λίστες υποχρεώσεων για τους χρήστες της. Η μικροσκοπική μας εφαρμογή θα παρέχει τις ακόλουθες δυνατότητες:
Η εφαρμογή μας έχει μόνο δύο μοντέλα: Χρήστη και TODO. Με τη βοήθεια του SQLAlchemy, της εργαλειοθήκης βάσης δεδομένων για την Python, μπορούμε να εκφράσουμε τα μοντέλα μας ως εξής:
class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True, index=True) lname = Column(String) fname = Column(String) email = Column(String, unique=True, index=True) todos = relationship('TODO', back_populates='owner', cascade='all, delete-orphan') class TODO(Base): __tablename__ = 'todos' id = Column(Integer, primary_key=True, index=True) text = Column(String, index=True) completed = Column(Boolean, default=False) owner_id = Column(Integer, ForeignKey('users.id')) owner = relationship('User', back_populates='todos')
Μόλις τα μοντέλα μας είναι έτοιμα, ας γράψουμε το αρχείο διαμόρφωσης για το SQLAlchemy έτσι ώστε να ξέρει πώς να δημιουργήσει σύνδεση με τη βάση δεδομένων.
import os from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker SQLALCHEMY_DATABASE_URL = os.environ['SQLALCHEMY_DATABASE_URL'] engine = create_engine( SQLALCHEMY_DATABASE_URL) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base()
Ένα μεγάλο μέρος οποιουδήποτε έργου API αφορά τα συνηθισμένα πράγματα, όπως επικύρωση δεδομένων και μετατροπή. Ας το αντιμετωπίσουμε προτού προχωρήσουμε στο χειρισμό γραπτών αιτημάτων. Με το FastAPI, εκφράζουμε το σχήμα των εισερχόμενων / εξερχόμενων δεδομένων μας χρησιμοποιώντας pydantic μοντέλα και στη συνέχεια χρησιμοποιούμε αυτά τα pydantic μοντέλα για να πληκτρολογήσετε υπόδειξη και να απολαύσετε δωρεάν επικύρωση και μετατροπή δεδομένων. Λάβετε υπόψη ότι αυτά τα μοντέλα δεν σχετίζονται με τη ροή εργασίας της βάσης δεδομένων μας και καθορίζουν μόνο το σχήμα των δεδομένων που ρέουν μέσα και έξω από τη διεπαφή REST. Για να γράψετε pydantic μοντέλα, σκεφτείτε όλους τους τρόπους με τους οποίους οι πληροφορίες χρήστη και TODO θα εισέρχονται και εξέρχονται.
Παραδοσιακά, ένας νέος χρήστης θα εγγραφεί στην υπηρεσία TODO μας και ένας υπάρχων χρήστης θα συνδεθεί. Και οι δύο αυτές αλληλεπιδράσεις αφορούν τις πληροφορίες χρήστη, αλλά το σχήμα των δεδομένων θα είναι διαφορετικό. Χρειαζόμαστε περισσότερες πληροφορίες από τους χρήστες κατά την εγγραφή και ελάχιστες (μόνο email και κωδικός πρόσβασης) κατά τη σύνδεση. Αυτό σημαίνει ότι χρειαζόμαστε δύο pydantic μοντέλα για να εκφράσουμε αυτά τα δύο διαφορετικά σχήματα πληροφοριών χρήστη.
Στην εφαρμογή TODO, ωστόσο, θα αξιοποιήσουμε την ενσωματωμένη υποστήριξη OAuth2 στο FastAPI για ροή σύνδεσης βάσει JSON Web Tokens (JWT). Πρέπει απλώς να ορίσουμε ένα UserCreate
σχήμα εδώ για να καθορίσετε δεδομένα που θα ρέουν στο τελικό σημείο εγγραφής και ένα UserBase
σχήμα για επιστροφή ως απόκριση σε περίπτωση που η διαδικασία εγγραφής είναι επιτυχής.
from pydantic import BaseModel from pydantic import EmailStr class UserBase(BaseModel): email: EmailStr class UserCreate(UserBase): lname: str fname: str password: str
Εδώ, επισημάνουμε το επώνυμο, το όνομα και τον κωδικό πρόσβασης ως συμβολοσειρά, αλλά μπορεί να ενισχυθεί περαιτέρω χρησιμοποιώντας το pydantic περιορισμένες χορδές που επιτρέπουν ελέγχους όπως το ελάχιστο μήκος, το μέγιστο μήκος και τους regexes.
Για να υποστηρίξουμε τη δημιουργία και την καταχώριση στοιχείων TODO, ορίζουμε το ακόλουθο σχήμα:
class TODOCreate(BaseModel): text: str completed: bool
Για να υποστηρίξουμε την ενημέρωση ενός υπάρχοντος στοιχείου TODO, ορίζουμε ένα άλλο σχήμα:
class TODOUpdate(TODOCreate): id: int
Με αυτό, τελειώσαμε με τον καθορισμό σχημάτων για όλες τις ανταλλαγές δεδομένων. Τώρα στρέφουμε την προσοχή μας στους αιτούντες χειριστές όπου αυτά τα σχήματα θα χρησιμοποιηθούν για να κάνουν όλη τη βαριά άρση της μετατροπής και επικύρωσης δεδομένων δωρεάν.
Αρχικά, ας επιτρέψουμε στους χρήστες να εγγραφούν, καθώς όλες οι υπηρεσίες μας πρέπει να έχουν πρόσβαση από έναν πιστοποιημένο χρήστη. Γράφουμε το χειριστή πρώτου αιτήματος χρησιμοποιώντας το UserCreate
και UserBase
σχήμα που ορίζεται παραπάνω.
@app.post('/api/users', response_model=schemas.User) def signup(user_data: schemas.UserCreate, db: Session = Depends(get_db)): '''add new user''' user = crud.get_user_by_email(db, user_data.email) if user: raise HTTPException(status_code=409, detail='Email already registered.') signedup_user = crud.create_user(db, user_data) return signedup_user
Υπάρχουν πολλά πράγματα σε αυτό το σύντομο κομμάτι κώδικα. Χρησιμοποιήσαμε έναν διακοσμητή για να καθορίσουμε το ρήμα HTTP, το URI και το σχήμα επιτυχημένων απαντήσεων. Προκειμένου να διασφαλιστεί ότι ο χρήστης έχει υποβάλει τα σωστά δεδομένα, πληκτρολογήσαμε την υπόδειξη του σώματος αιτήματος με ένα προηγούμενο καθορισμένο UserCreate
σχήμα. Η μέθοδος καθορίζει μια άλλη παράμετρο για τη λήψη μιας λαβής στη βάση δεδομένων - αυτή είναι η εξάρτηση εξάρτησης σε δράση και συζητείται αργότερα σε αυτό το σεμινάριο.
Θέλουμε τα ακόλουθα χαρακτηριστικά ασφαλείας στην εφαρμογή μας:
Για κατακερματισμό κωδικού πρόσβασης, μπορούμε να χρησιμοποιήσουμε το Passlib. Ας καθορίσουμε τις λειτουργίες που χειρίζονται τον κατακερματισμό του κωδικού πρόσβασης και ελέγχουμε αν ο κωδικός πρόσβασης είναι σωστός
from passlib.context import CryptContext pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto') def verify_password(plain_password, hashed_password): return pwd_context.verify(plain_password, hashed_password) def get_password_hash(password): return pwd_context.hash(password) def authenticate_user(db, email: str, password: str): user = crud.get_user_by_email(db, email) if not user: return False if not verify_password(password, user.hashed_password): return False return user
Για να ενεργοποιήσουμε τον έλεγχο ταυτότητας με βάση το JWT, πρέπει να δημιουργήσουμε JWT καθώς και να τα αποκωδικοποιήσουμε για να λάβουμε διαπιστευτήρια χρήστη. Ορίζουμε τις ακόλουθες λειτουργίες για να παρέχουμε αυτήν τη λειτουργικότητα.
# install PyJWT import jwt from fastapi.security import OAuth2PasswordBearer SECRET_KEY = os.environ['SECRET_KEY'] ALGORITHM = os.environ['ALGORITHM'] def create_access_token(*, data: dict, expires_delta: timedelta = None): to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(minutes=15) to_encode.update({'exp': expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt def decode_access_token(db, token): credentials_exception = HTTPException( status_code=HTTP_401_UNAUTHORIZED, detail='Could not validate credentials', headers={'WWW-Authenticate': 'Bearer'}, ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) email: str = payload.get('sub') if email is None: raise credentials_exception token_data = schemas.TokenData(email=email) except PyJWTError: raise credentials_exception user = crud.get_user_by_email(db, email=token_data.email) if user is None: raise credentials_exception return user
Τώρα, θα ορίσουμε ένα τελικό σημείο σύνδεσης και θα εφαρμόσουμε τη ροή κωδικού πρόσβασης OAuth2. Αυτό το τελικό σημείο θα λάβει ένα email και έναν κωδικό πρόσβασης. Θα ελέγξουμε τα διαπιστευτήρια έναντι της βάσης δεδομένων και, κατά την επιτυχία, θα εκδώσουμε ένα διακριτικό ιστού JSON στον χρήστη.
Για να λάβουμε τα διαπιστευτήρια, θα χρησιμοποιήσουμε το OAuth2PasswordRequestForm
, το οποίο αποτελεί μέρος των βοηθητικών προγραμμάτων ασφαλείας της FastAPI.
@app.post('/api/token', response_model=schemas.Token) def login_for_access_token(db: Session = Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends()): '''generate access token for valid credentials''' user = authenticate_user(db, form_data.username, form_data.password) if not user: raise HTTPException( status_code=HTTP_401_UNAUTHORIZED, detail='Incorrect email or password', headers={'WWW-Authenticate': 'Bearer'}, ) access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token(data={'sub': user.email}, expires_delta=access_token_expires) return {'access_token': access_token, 'token_type': 'bearer'}
Έχουμε ρυθμίσει το τελικό σημείο σύνδεσης που παρέχει JWT σε έναν χρήστη κατά την επιτυχή σύνδεση. Ο χρήστης μπορεί να αποθηκεύσει αυτό το διακριτικό στον τοπικό χώρο αποθήκευσης και να το δείξει στο πίσω μέρος μας ως κεφαλίδα Εξουσιοδότησης. Τα τελικά σημεία που αναμένουν πρόσβαση μόνο από συνδεδεμένους χρήστες μπορούν να αποκωδικοποιήσουν το διακριτικό και να μάθουν ποιος είναι ο αιτών. Αυτό το είδος εργασίας δεν συνδέεται με ένα συγκεκριμένο τελικό σημείο, αλλά είναι κοινή λογική που χρησιμοποιείται σε όλα τα προστατευμένα τελικά σημεία. Είναι καλύτερο να ρυθμίσετε τη λογική αποκωδικοποίησης διακριτικών ως εξάρτηση που μπορεί να χρησιμοποιηθεί σε οποιονδήποτε χειριστή αιτημάτων.
Στο FastAPI-speak, οι λειτουργίες λειτουργίας διαδρομής (χειριστές αιτήσεων) θα εξαρτηθούν τότε από get_current_user
. Το get_current_user
η εξάρτηση πρέπει να έχει σύνδεση με τη βάση δεδομένων και να συνδέεται με το FastAPI | _ _ _ | λογική για να αποκτήσετε ένα διακριτικό. Θα επιλύσουμε αυτό το ζήτημα κάνοντας OAuth2PasswordBearer
εξαρτάται από άλλες λειτουργίες. Με αυτόν τον τρόπο, μπορούμε να ορίσουμε αλυσίδες εξάρτησης, που είναι μια πολύ ισχυρή ιδέα.
get_current_user
Πριν γράψουμε τις λειτουργίες λειτουργίας διαδρομής για TODO Δημιουργία, Ανάγνωση, Ενημέρωση, Διαγραφή (CRUD), καθορίζουμε τις ακόλουθες βοηθητικές συναρτήσεις για την εκτέλεση πραγματικών CRUD στο db.
def get_db(): '''provide db session to path operation functions''' try: db = SessionLocal() yield db finally: db.close() def get_current_user(db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)): return decode_access_token(db, token) @app.get('/api/me', response_model=schemas.User) def read_logged_in_user(current_user: models.User = Depends(get_current_user)): '''return user settings for current user''' return current_user
Αυτές οι συναρτήσεις επιπέδου db θα χρησιμοποιηθούν στα ακόλουθα τελικά σημεία REST:
def create_todo(db: Session, current_user: models.User, todo_data: schemas.TODOCreate): todo = models.TODO(text=todo_data.text, completed=todo_data.completed) todo.owner = current_user db.add(todo) db.commit() db.refresh(todo) return todo def update_todo(db: Session, todo_data: schemas.TODOUpdate): todo = db.query(models.TODO).filter(models.TODO.id == id).first() todo.text = todo_data.text todo.completed = todo.completed db.commit() db.refresh(todo) return todo def delete_todo(db: Session, id: int): todo = db.query(models.TODO).filter(models.TODO.id == id).first() db.delete(todo) db.commit() def get_user_todos(db: Session, userid: int): return db.query(models.TODO).filter(models.TODO.owner_id == userid).all()
Ας γράψουμε μερικές δοκιμές για το API TODO. Το FastAPI παρέχει ένα @app.get('/api/mytodos', response_model=List[schemas.TODO]) def get_own_todos(current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db)): '''return a list of TODOs owned by current user''' todos = crud.get_user_todos(db, current_user.id) return todos @app.post('/api/todos', response_model=schemas.TODO) def add_a_todo(todo_data: schemas.TODOCreate, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db)): '''add a TODO''' todo = crud.create_meal(db, current_user, meal_data) return todo @app.put('/api/todos/{todo_id}', response_model=schemas.TODO) def update_a_todo(todo_id: int, todo_data: schemas.TODOUpdate, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db)): '''update and return TODO for given id''' todo = crud.get_todo(db, todo_id) updated_todo = crud.update_todo(db, todo_id, todo_data) return updated_todo @app.delete('/api/todos/{todo_id}') def delete_a_meal(todo_id: int, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db)): '''delete TODO of given id''' crud.delete_meal(db, todo_id) return {'detail': 'TODO Deleted'}
τάξη που βασίζεται στη δημοφιλή βιβλιοθήκη αιτημάτων και μπορούμε να εκτελέσουμε τις δοκιμές με το Pytest.
Για να βεβαιωθούμε ότι μόνο οι συνδεδεμένοι χρήστες μπορούν να δημιουργήσουν ένα TODO, μπορούμε να γράψουμε κάτι σαν αυτό:
TestClient
Η ακόλουθη δοκιμή ελέγχει το τελικό σημείο σύνδεσης και δημιουργεί ένα JWT εάν παρουσιάζεται με έγκυρα διαπιστευτήρια σύνδεσης.
from starlette.testclient import TestClient from .main import app client = TestClient(app) def test_unauthenticated_user_cant_create_todos(): todo=dict(text='run a mile', completed=False) response = client.post('/api/todos', data=todo) assert response.status_code == 401
Έχουμε ολοκληρώσει την εφαρμογή μιας πολύ απλής εφαρμογής TODO χρησιμοποιώντας το FastAPI. Μέχρι τώρα, έχετε δει τη δύναμη των υποδείξεων τύπου να χρησιμοποιούνται σωστά στον καθορισμό του σχήματος των εισερχόμενων και εξερχόμενων δεδομένων μέσω της διεπαφής REST. Ορίζουμε τα σχήματα σε ένα μέρος και αφήνουμε στο FastAPI να εφαρμόζει επικύρωση και μετατροπή δεδομένων. Το άλλο αξιοσημείωτο χαρακτηριστικό είναι η έγχυση εξάρτησης. Χρησιμοποιήσαμε αυτήν την ιδέα για να συσκευάσουμε την κοινή λογική της απόκτησης σύνδεσης βάσης δεδομένων, αποκωδικοποίησης του JWT για λήψη του τρέχοντα συνδεδεμένου χρήστη και υλοποίησης του απλού OAuth2 με κωδικό πρόσβασης και φορέα. Είδαμε επίσης πώς μπορούν οι αλυσίδες να συνδέονται μεταξύ τους.
Μπορούμε εύκολα να εφαρμόσουμε αυτήν την ιδέα για να προσθέσουμε χαρακτηριστικά όπως η πρόσβαση βάσει ρόλου. Εκτός αυτού, γράφουμε συνοπτικό και ισχυρό κώδικα χωρίς να μάθουμε τις ιδιαιτερότητες ενός πλαισίου. Με απλά λόγια, το FastAPI είναι μια συλλογή ισχυρών εργαλείων που δεν χρειάζεται να μάθετε επειδή είναι απλώς μοντέρνα Python. Καλα να περνατε.
Το FastAPI είναι ένα πλαίσιο Python και ένα σύνολο εργαλείων που επιτρέπει στους προγραμματιστές να χρησιμοποιούν μια διεπαφή REST για να καλούν λειτουργίες που χρησιμοποιούνται συνήθως για την εφαρμογή εφαρμογών. Έχει πρόσβαση μέσω ενός REST API για να καλέσει κοινά δομικά στοιχεία για μια εφαρμογή. Σε αυτό το παράδειγμα, ο συγγραφέας χρησιμοποιεί το FastAPI για τη δημιουργία λογαριασμών, τη σύνδεση και τον έλεγχο ταυτότητας.
Όπως όλες οι διεπαφές REST, το FastAPI καλείται από τον κωδικό σας. Προσφέρει λειτουργίες όπως υποδείξεις τύπου για δεδομένα που έχουν μεταφερθεί, έγχυση εξάρτησης και έλεγχο ταυτότητας, ώστε να μην χρειάζεται να γράψετε τις δικές σας λειτουργίες.
Ενώ ένα πλαίσιο ανοιχτού κώδικα, το FastAPI είναι πλήρως έτοιμο για παραγωγή, με εξαιρετική τεκμηρίωση, υποστήριξη και εύχρηστη διεπαφή. Μπορεί να χρησιμοποιηθεί για τη δημιουργία και εκτέλεση εφαρμογών που είναι τόσο γρήγορες όσο αυτές που γράφονται σε άλλες γλώσσες δέσμης ενεργειών.
ποιο από τα παρακάτω θα ήταν παράδειγμα ελέγχου ταυτότητας που βασίζεται σε διακριτικά;
Μπορεί να είναι εάν αυτή η διεπαφή είναι καλά καθορισμένη και ο υποκείμενος κώδικας έχει βελτιστοποιηθεί. Η τεκμηρίωση ισχυρίζεται ότι το FastAPI αποδίδει καθώς και το Node.js και το Go.