Σάββατο 28 Νοεμβρίου 2020

Βάση δεδομένων στο PythonAnywhere - MySQL on PythonAnywhere

 

Στην πρώτη έκδοση της εφαρμογής scratchpad για να αποθηκεύονται τα σχόλια είχαμε δημιουργήσει ένα αντικείμενο (comments) μέσα στον κώδικα. Αυτό όμως «καθάριζε» κάθε φορά που αλλάζαμε τον κώδικα, δεν ήταν μόνιμο, οι εγγραφές των χρηστών χάνονταν. Τώρα πρέπει να στήσουμε μια βάση δεδομένων για αυτή τη δουλειά με το πλεονέκτημα ότι μπορούμε να τη διαχειριστούμε αργότερα όπως κάνουμε με ένα φύλλο excel!

Υπάρχουν αρκετοί τρόποι να στήσουμε βάση δεδομένων

  • SQLite, απλή και ελαφριά βάση που αποθηκεύει τα πάντα σε ένα σημείο στο δίσκο μας.
  • MySQL, τρέχει σε κάποιο server και αποθηκεύει τα πάντα εκεί με το πλεονέκτημα ότι διαφορετικά προγράμματα σε διαφορετικές μηχανές έχουν ταυτόχρονα πρόσβαση σε αυτή!
  • PostgreSQL (ή σκέτο Postgres), λειτουργεί όπως η MySQL, χειρίζεται μεγαλύτερα πακέτα και προφανώς απαιτεί περισσότερους πόρους.
  • MongoDB, νέα, μοδάτη, λειτουργεί ως server αλλά με πιο χαλαρή δομή.

Η SQLite τρέχει αργά στο PythonAnywhere, και δεν κλιμακώνεται καλά σε μεγαλύτερες εφαρμογές. Η Postgres προσφέρεται στο  PythonAnywhere αλλά πρέπει να την πληρώσουμε γιατί έχει περισσότερες απαιτήσεις πόρων. Το PythonAnywhere δεν ενσωματώνει υποστήριξη για τη MongoDB. Άρα θα παίξουμε με την MySQL, που είναι διαθέσιμη στο δωρεάν λογαριασμό μας στο PythonAnywhere.

Η αλήθεια είναι ότι ο προγραμματιστής δε χρειάζεται να ανησυχεί σε πια βάση τρέχει η εφαρμογή του. Υπάρχουν βιβλιοθήκες  ORMs (Object-Relational Mappers = Σχεσιακές αντιστοιχίσεις αντικειμένων) (;) που εξασφαλίζουν ότι η εφαρμογή μας θα τρέξει άσχετα από την υποκείμενη βάση δεδομένων.

Εδώ θα χρησιμοποιήσουμε την SQLAlchemy που λειτουργεί άψογα με το Flask.

Είμαστε μέσα στο λογαριασμό μας PythonAnywhere. Πατάμε το κουμπί Databases



Δημιουργείστε και σημειώστε κάπου ένα μοναδικό password από 8 έως 16 χαρακτήρες για πρόσβαση στη βάση. Προσοχή αυτό το password θα φαίνεται ολοφάνερα σαν απλό κείμενο μέσα στον κώδικα!

Πατάμε το κουμπί Initialize MySQL και περιμένουμε ένα λεπτό να ενεργοποιηθεί η βάση. Αμέσως μετά, κάτω από την ένδειξη MySQL databases βλέπουμε μια αυτόματα δημιουργημένη βάση με όνομα yourusername$default.


 

 

Καλό είναι για κάθε εφαρμογή να δημιουργούμε τη δική της βάση δεδομένων. Έτσι στο πεδίο “Create database” γράψτε comments  και πατήστε το κουμπί “Create database

 Τώρα λοιπόν έχουμε δύο βάσεις:

yourusername$comments

yourusername$default

 

Τώρα θα συνδέσουμε τη βάση στον κώδικα!

Ανοίξτε μια δεύτερη καρτέλα και ανοίξτε το αρχείο Flask_app.py που έχει τον κώδικα. Για ευκολία κρατήστε την καρτέλα  Databases στο αριστερό μισό της οθόνης και την καρτέλα  με τον κώδικα στο δεξί.

O κώδικας που γράφαμε τις προάλλες βρίσκεται  στην καρτέλα Files στο directory mysite στο αρχείο Flask_app.py. Στο ίδιο αρχείο φτάνουμε από την καρτέλα Web στην παράγραφο Code γράφει home/username/ mysite και πατάμε το Go to Directory.



Εισάγουμε το SQLAlchemy  στον κώδικα.Μέσα στον κώδικα του αρχείου flask_app.py στην τρίτη γραμμή κάτω δηλαδή από τα άλλα import, γράφουμε αυτό:

from flask_sqlalchemy import SQLAlchemy

 

Μετά συνδέουμε τη βάση με τον κώδικα: ακριβώς κάτω από τις γραμμές

app = Flask(__name__)
app.config["DEBUG"] = True

προσθέτουμε τις παρακάτω γραμμές με απόλυτη προσοχή στα εισαγωγικά, στα σημεία στίξης και στις εσοχές:

SQLALCHEMY_DATABASE_URI = "mysql+mysqlconnector://{username}:{password}@{hostname}/{databasename}".format(
    username="the username from the 'Databases' tab",
    password="the password you set on the 'Databases' tab",
    hostname="the database host address from the 'Databases' tab",
    databasename="the database name you chose, probably yourusername$comments",
)
app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI
app.config["SQLALCHEMY_POOL_RECYCLE"] = 299
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)

Τώρα θα θέσουμε τις σωστές τιμές στα πεδία που εμφανίζονται κατακόρυφα (μετά τη λέξη format) – προσοχή στα εισαγωγικά και στα κόμματα! 

username="εδώ βάζω το username όπως φαίνεται στην καρτέλα Databases",

password="εδώ βάζω το password που έθεσα για να δημιουργήσω τη βάση",

hostname="εδώ βάζω το host address όπως φαίνεται στην καρτέλα Databases",

databasename=" εδώ βάζω το database name όπως φαίνεται στην καρτέλα Databases προφανώς αυτή που τελειώνει σε $comments",

Είστε έτοιμοι;  αν αργότερα δε φορτώνει η βάση, έχουμε κάνει λάθος κάποιο χαρακτήρα σε αυτό το βήμα!

Εξηγήσεις για τις εντολές.

Η πρώτη από τις εντολές δημιουργεί μια μεταβλητή για τη σύνδεση με τη βάση. Η δεύτερη δρομολογεί τη βάση στις ρυθμίσεις της εφαρμογής. Η τρίτη (POOL_RECYCLE) θέτει ένα χρονικό περιορισμό για την εύρυθμη λειτουργία της βάσης. Περισσότερες πληροφορίες στο υπόμνημα «χρονικά όρια σύνδεσης» (connection timeouts) στο τέλος. Η τέταρτη εντολή (TRACK_MODIFICATIONS) απενεργοποιεί ένα χαρακτηριστικό της SQLAlchemy (που δεν το χρειαζόμαστε επί του παρόντος) γιατί η παρακολούθηση μεταβολών θα εμφανίζει δυσνόητα (για τον αρχάριο) μηνύματα. Η επόμενη γραμμή δημιουργεί το κατάλληλο αντικείμενο με τις ρυθμίσεις που μόλις ορίσαμε.


 Πατάμε save και reload και αν όλα πάνε καλά, δε θα παρουσιαστεί μέσα στο Flask κανένα μήνυμα λάθους. Αν παρουσιαστεί, διορθώνουμε αναλόγως.

Τώρα πρέπει να ορίσουμε στη βάση δεδομένων το μοντέλο του πίνακα που θέλουμε να χρησιμοποιήσουμε και πώς θέλουμε να στήνονται οι εισερχόμενες πληροφορίες.

Μια βάση δεδομένων μοιάζει με ένα excel: μπορεί να έχει πολλούς πίνακες και ο κάθε πίνακας να έχει στήλες και γραμμές. Για κάθε κελί (στήλη) ορίζουμε τι είδους πληροφορία μπορεί να αποθηκευτεί! Για παράδειγμα μπορούμε να ορίσουμε ότι το περιεχόμενο του κελιού θα είναι κείμενο, αριθμός, ημερομηνία, τηλέφωνο κ.ο.κ.  Επίσης πρέπει να ορίσουμε αν είναι υποχρεωτικό πεδίο ή προαιρετικό! Από την πλευρά της η Python βλέπει κάθε τέτοιο ορισμό – κάθε ξεχωριστό πεδίο - ως «τάξη» (class).

Η δική μας εφαρμογή έχει μόνο ένα είδος πληροφορίας για αποθήκευση (απλό κείμενο) έτσι χρειαζόμαστε μόνο ένα πίνακα με μία στήλη, τη στήλη comments. Κάθε νέα εγγραφή που θα εκτελεί κάποιος χρήστης στην ιστοσελίδα μας, θα αποθηκεύεται στην ίδια στήλη σε νέα γραμμή κάτω από τις προηγούμενες!

Ελάτε να στήσουμε το μοντέλο (model) και την τάξη (class) όπως τα καταλαβαίνει η Python.

Προσθέτουμε αυτές τις γραμμές ακριβώς πάνω από τη γραμμή

comments = []:

class Comment(db.Model):
 
    __tablename__ = "comments"
 
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.String(4096))

 αυτό κοντολογίς λέει «θέλω  μια κλάση με όνομα comment και το εξής μοντέλο: μια (κρυφή) στήλη με αύξοντα αριθμό (μπορεί να χρησιμοποιηθεί αργότερα) και μια στήλη με απλό κείμενο». Αν στο μέλλον θελήσουμε να προσθέσουμε πράγματα (π.χ. ημερομηνία) τροποποιούμε ανάλογα το μοντέλο μας – όπως στο σχετικό άρθρο που θα αναρτηθεί εδώ).


 Πατάμε save και reload.

Τώρα θα πείσουμε την SQLAlchemy να δημιουργήσει τον πίνακα. Αυτό το βήμα μπορεί να σας φανεί παράξενο, αλλά δε θα χρειαστεί να επανέλθουμε σε αυτό. Στο κάτω μέρος του αρχείου  Flask_app.py που επεξεργαζόμαστε, υπάρχει το κουμπί Bash Console Here. 



Το πατάμε και ανοίγει στο κάτω μέρος της σελίδας ένας διερμηνέας Python, που στην πρώτη γραμμή γράφει:
~/mysite (master)$

Πληκτρολογούμε ipython3.7 και πατάμε Enter

Πληκτρολογούμε from flask_app import db και πατάμε Enter

Πληκτρολογούμε db.create_all() και πατάμε Enter

Βλέπουμε αυτή την οθόνη:

 


Τώρα θα ελέγξουμε αν η βάση δημιουργήθηκε σωστά. Επιστρέφουμε στην καρτέλα Databases (υποθέτω ότι την έχουμε αφήσει ανοιχτή στο αριστερό μέρος της οθόνης μας) και πατάμε πάνω στο (yourusername$comments). Αυτό ανοίγει μια νέα κονσόλα.

Μετά το mysql> πληκτρολογούμε show tables; και πατάμε Enter

Ξανά μετά το mysql> πληκτρολογούμε describe comments; και πατάμε Enter

 Πρέπει να βλέπουμε αυτό:


Ουσιαστικά το show tables μας έδειξε ότι έχουμε ένα πίνακα με το όνομα comments και το describe comments μας δείχνει ότι έχουμε 2 στήλες (εδώ εμφανίζονται σε γραμμές με τις ιδιότητές τους!) μία στήλη με το id που είναι ακέραιος και μια στήλη με το "περιεχόμενο" που είναι απλό κείμενο.

Υπενθύμιση: Στην πρώτη έκδοση της εφαρμογής για να αποθηκεύονται τα σχόλια είχαμε δημιουργήσει ένα αντικείμενο (comments) μέσα στον κώδικα. Τώρα που στήσαμε βάση δεδομένων πρέπει να πετάξουμε το αντικείμενο και να προσθέσουμε τη βάση!

Μέσα στον κώδικα του αρχείου flask_app.py

διαγράφουμε τη γραμή

comments = []

 Επίσης αντικαθιστούμε αυτή την παλιά γραμμή

    return render_template("main_page.html", comments=comments)

με αυτή τη νέα (προσοχή στις εσοχές!)

return render_template("main_page.html", comments=Comment.query.all())

 

Τέλος αντικαθιστούμε αυτή τη γραμμή

comments.append(request.form["contents"])

Με αυτές τις γραμμές (προσοχή στις εσοχές!)

    comment = Comment(content=request.form["contents"])

    db.session.add(comment)

    db.session.commit()

Η πρώτη γραμμή δημιουργεί το αντικείμενο, η δεύτερη στέλνει την πληροφορία για αποθήκευση στη βάση (η επικοινωνία μένει ανοιχτή γιατί μπορεί να θέλαμε να στείλουμε και άλλα πράγματα, μέχρι που) η τρίτη κλείνει την επικοινωνία και αποθηκεύει.

 


 Πατάμε save και reload.

Προσοχή! Πρέπει να θυμόμαστε ότι η εφαρμογή μας έχει δύο χέρια: τον κώδικα στο Flask και το template που λέει στην ιστοσελίδα πώς θα παρουσιάζει το περιεχόμενο. Επομένως πρέπει να αλλάξουμε λίγο το template.

Μέσα στο directory templates ανοίγουμε το αρχείο main_page.html.

Κάτω από τις γραμμές

        {% for comment in comments %}
            <div class="row">

αντικαθιστούμε το παλιό

{{ comment }}

με το νέο

{{ comment.content }}


 Πατάμε save και reload.

Είμαστε έτοιμοι! Πάμε στη σελίδα να ελέγξουμε αν δουλεύει σωστά και μετά ελέγχουμε τη βάση δεδομένων αν καταγράφει σωστά.

H σελίδα: Πατάμε την καρτέλα Web και βρίσκουμε το σύνδεσμο (link). Γράψτε ένα σχόλιο και ανεβάστε το. Εμφανίζεται σωστά; Μπράβο!

Η βάση: Στην καρτέλα Consoles έχετε 2 κονσόλες που χρησιμοποιήσατε πρόσφατα. Πατήστε την κονσόλα MySQL.

Μετά το mysql> πληκτρολογούμε select * from comments; και πατάμε Enter

Εμφανίζεται ο πίνακας με τα νέα σχόλια που γράψαμε στην ιστοσελίδα μας! δηλαδή κάτι τέτοιο:


 

Συγχαρητήρια!

Σε αυτό το άρθρο κάναμε 3 πράγματα:

1. στήσαμε και ελέγξαμε τη βάση δεδομένων γράφοντας εντολές κατευθείαν πάνω στις κατάλληλες κονσόλες.

2. βελτιώσαμε τον κώδικα (στο flask_app.py)

from flask import Flask, redirect, render_template, request, url_for

from flask_sqlalchemy import SQLAlchemy

 

app = Flask(__name__)

app.config["DEBUG"] = True

 

SQLALCHEMY_DATABASE_URI = "mysql+mysqlconnector://{username}:{password}@{hostname}/{databasename}".format(

    username="the username from the 'Databases' tab",

    password="the password you set on the 'Databases' tab",

    hostname="the database host address from the 'Databases' tab",

    databasename="the database name you chose, probably yourusername$comments",

)

app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI

app.config["SQLALCHEMY_POOL_RECYCLE"] = 299

app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

 

db = SQLAlchemy(app)

class Comment(db.Model):

    __tablename__ = "comments"

    id = db.Column(db.Integer, primary_key=True)

    content = db.Column(db.String(4096)) 

@app.route("/", methods=["GET", "POST"])

def index():

    if request.method == "GET":

        return render_template("main_page.html", comments=Comment.query.all())

    comment = Comment(content=request.form["contents"])

    db.session.add(comment)

    db.session.commit()

    return redirect(url_for('index'))

3. αλλάξαμε το template (στο main_page.html)

 

Εδώ το ΥΠΟΜΝΗΜΑ που υποσχέθηκα: 

 Χρονικά όρια σύνδεσης connection timeouts.

Το άνοιγμα μιας σύνδεσης από την ιστοσελίδα προς το server που φιλοξενεί τη βάση δεδομένων, χρειάζεται καμπόσο χρόνο. Στο σενάριο που ανοίγει μια σύνδεση με κάθε επίσκεψη στην ιστοσελίδα μας, η καθυστέρηση θα ήταν υπολογίσιμη. Αντί για αυτό η SQLAlchemy δημιουργεί μια δεξαμενή συνδέσεων. Δημιουργεί ένα πλήθος συνδέσεων και τα επαναχρησιμοποιεί. Για κάθε επίσκεψη στην ιστοσελίδα (που απαιτεί δημιουργία σύνδεσης) η SQLAlchemy προσφέρει μια σύνδεση από τη δεξαμενή. Όταν κλείσει η ιστοσελίδα (και η σύνδεση) η SQLAlchemy επιστρέφει τη σύνδεση στη διαθεσιμότητα της δεξαμενής. Αν η δεξαμενή αδειάσει (αν χρησιμοποιηθούν όλες οι συνδέσεις) η SQLAlchemy παράγει και άλλες συνδέσεις, ώστε η δεξαμενή να έχει απόθεμα. Ωστόσο για την εύρυθμη λειτουργία των server οι εφεδρικές συνδέσεις κλείνουν μετά από συγκεκριμένο χρονικό διάστημα. Για το PythonAnywhere αυτό το χρονικό όριο σύνδεσης (connection timeout) έχει οριστεί στα 300 δευτερόλεπτα. Εμείς θέτουμε το δικό μας χρονικό όριο στα 299 δευτερόλεπτα, ώστε να μην βρεθούμε με μία σύνδεση στο χέρι, που εν τω μεταξύ έχει κλείσει από το server τη στιγμή που τη ζητάμε!

 

πηγή:https://blog.pythonanywhere.com/121/ 

Δεν υπάρχουν σχόλια: