שבוע 12 – יום 5: סביבה וירטואלית, Jinja2, העלאה ל־Heroku

מה היא סביבה וירטואלית?

נניח שאתם מתכנתים כמה וכמה פרויקטים על אותו מחשב.

פרויקט אחד הוא של העבודה, בו אתם עורכים קוד שנכתב לפני 5 שנים ומשתמש במודולים ישנים יחסית.
אחד המודולים, Flask, נניח, הוא בגרסה ישנה מאוד (נניח, גרסה 0.12), ושדרוג שלו ישבור את המערכת.

הפרויקט השני הוא מערכת התרגילים של הקורס, שמשתמש בטכנולוגיה חדשה ונוצצת.
בין המודולים שמככּבים בו נמצא Flask בגרסתו החדשה ביותר – גרסה 1.1.

בפרויקט שלישי משל עצמכם אתם משתמשים במודולים אחרים, אבל חלק מהמודולים שבהם אתם משתמשים, נסמכים בעצמם על מודולים אחרים.
בין המודולים, ישנו גם מודול A שבעצמו נסמך מודול B – הרי הוא Flask בגרסה 1.0.

כשאתם מנסים לעבור בין פרויקטים אתם מגלים שגרסה 0.12 של Flask אינה תואמת עם גרסה 1.1,
וגרסה 1.1 מתנהגת מעט שונה מגרסה 1.0.

מובן שישנה בעיה: לא פרקטי להסיר את Flask ולהתקין את הגרסה הרצויה כל פעם שנעבור לעבוד על פרויקט אחר.

הפתרון? סביבות וירטואליות!

סביבה וירטואלית מאפשרת לנו להגדיר מעין קופסה מבודדת שבה רץ פייתון בלי כל המודולים שהתקנו על המחשב.
אם נרצה, נוכל להתקין עבור המערכת שאנחנו כותבים בעבודה סביבה וירטואלית (דמיינו חדר מבו‏ּדד וסטרילי) שבה מותקנים רק המודולים ששייכים לפרויקט שלנו.
כשנרצה לעבור לפרויקט אחר, נניח, לזה של מערכת הגשת התרגילים בקורס – נוכל להגדיר עבורו סביבה וירטואלית משלו, שבה יהיו מותקנים רק המודולים שקשורים למערכת הגשת התרגילים בקורס.

אלו שתי סביבות שונות, נפרדות אחת מהשנייה, שלא יודעות על קיומן אחת של השנייה, או על המודולים שהותקנו אחת אצל השנייה.
זה מאפשר לנו (ולכל מי שיעבוד על המערכת שלנו) להפעיל כל אחת מהסביבות בנפרד, ולעבוד עליהן באופן בלתי תלוי.

איך עושים את זה?

כל מה שצריך כדי ליצור סביבה וירטואלית, זה להריץ בשורת הפקודה:
python -m venv PROJECT_NAME

הפקודה הזו תיצור תיקייה חדשה בשם PROJECT_NAME, שבתוכה יהיו תיקיות וקבצים.
התיקיות והקבצים הללו הם מה שמאפשר לסביבה הווירטואלית לפעול.
מרובם אתם רוצים להתעלם בשלב הזה, אבל אתם עדיין צריכים להיכנס לתוך הסביבה הווירטואלית כדי שתפעל.

כדי להפעיל את הסביבה הווירטואלית, אם כך, תצטרכו לרשום:

  • בווינדוס: PROJECT_NAME\Scripts\activate.bat
  • ב־MacOS/בלינוקס: source PROJECT_NAME/bin/activate

וזהו. כעת אתם בתוך הסביבה הווירטואלית.
ליד שורת הפקודה שלכם אמורה להתווסף המילה PROJECT_NAME, כדי להזכיר לכם שזה המצב.
תוכלו לצאת מהסביבה הווירטואלית בכל זמן אם תכתבו deactivate.

הסביבה הווירטואלית תקפה רק בחלון שבו הפעלתם אותה.
אם תריצו פקודות בחלון אחר אתם לא תפעלו תחת הסביבה הווירטואלית ותשפיעו על גרסת הפייתון “הראשית” שמותקנת אצלכם במחשב.

כדי להתקין מודול חדש תוכלו להשתמש ב־pip, כרגיל.
שימו לב שהתקנת המודול תתבצע בתוך הסביבה הווירטואלית ולא תשפיע על גרסת הפייתון הראשית שמותקנת אצלכם במחשב.
כדי לשמור את כל ההתקנות שביצעתם בסביבה וירטואלית מסוימת ולשלוח אותם לחבריכם שיוכלו להתקין אותן בקלות, כתבו:

pip freeze > requirements.txt

הפקודה הזו תיצור קובץ בשם requirements.txt, שמכיל את כל החבילות שמותקנות אצלכם בסביבה.
אם תכתבו אותו כשאתם מחוץ לסביבה הווירטואלית, ישפכו לקובץ הזה כל החבילות שמותקנות אצלכם על הגרסה הראשית של פייתון, ולא דווקא מותקנות בסביבה הווירטואלית. משמע: חשוב לוודא שאתם בתוך הסביבה הווירטואלית שלכם לפני שהרצתם pip freeze.

טיפ: ודאו ש־requirements.txt נמצא אצלכם בתיקייה הראשית של הפרויקט. זה הנוהג, ויש אפילו שירותים שיאפשרו לכם להעלות חבילה של פייתון וידרשו שהקובץ הזה ישב שם.

כדי להתקין את כל החבילות שנמצאות בקובץ requirements.txt שקיבלתם מחבר או בתוך פרויקט כלשהו, היכנסו לסביבה הווירטואלית וכתבו:

pip install -r requirements.txt

זהו. אתם יודעים להשתמש בסביבות וירטואליות :slight_smile:

גרסאות נוספות

נוסף על השיטה שהוצגה פה פה, יש שיטות נוספות ליצור סביבות וירטואליות.
(קריאת העשרה, ממילא אין צורך לזכור את כל זה ותתקלו בדברים האלו ככל שתעבדו יותר עם פייתון)

  1. המודול virtualenv – אותו רעיון, אבל לא חלק מהמודולים שבאים עם פייתון ויש לו יותר פיצ’רים.
  2. המודול conda – שילוב מעניין בין מנהל חבילות לסביבה וירטואלית. לא בשימוש נרחב, אבל נוח יחסית. מאפשר גם לבחור את גרסת הפייתון שרוצים להשתמש בה, אם אתם עובדים על פרויקטים שתומכים בגרסאות פייתון שונות.
  3. המודול pipenv הוא מודול פופולרי מאוד, שבדומה ל־conda עשיר בפיצ’רים לניהול סביבה וחבילות. מעבר לתפקידה של הפקודה pipenv שיוצרת סביבה וירטואלית, היא גם יודעת לנהל גם את החבילות שמותקנות באותה סביבה. הטריק של pipenv הוא ליצור קובץ נפרד בשם Pipfile.lock שבו יש את גרסאות החבילות שהותקנו, ללא התלויות שלהם (יכולים לחשוב על היתרונות והחסרונות? פתחו על זה דיון בפורום).
  4. המודול poetry הוא מודול שמתחיל לתפוס תאוצה בשנה האחרונה. הוא דומה ל־pipenv אבל תומך בתקנים החדשים ביותר של השפה, ונראה שהוא עובד די טוב בפועל.

הקהילה די מפוצלת בנוגע לשיטת ניהול התלויות והסביבה של הפרויקט, כך שאתם מוזמנים לבחור בדרך המועדפת עליכם.
אשאיר את קריאת ההרחבה וההשוואה על כל אחד מהמודולים הללו עבורכם.

תבניות לאתר: Jinja2

כשאנחנו כותבים קוד לאתר שלנו קצה־לקצה, אנחנו כבר יודעים שנצטרך להשתמש לא מעט ב־HTML.
הצרה היא שכשאנחנו רוצים לשלב את ה־HTML הזה עם פייתון, פעמים רבות נצטרך להכניס לדף מידע באופן דינאמי. למשל:

  1. אם קיבלנו מ־API חיצוני רשימת עצמים (מזג האוויר בשבוע הקרוב, העמלות של השקל מול הדולר בשבוע האחרון, רשימת כל הפוקימונים שקיימים) ואנחנו רוצים לשלב אותם ב־HTML באמצעות לולאה.
  2. אם אנחנו רוצים להציג חלק מסוים בדף רק במקרים מסוימים. לדוגמה, במערכת התרגילים מוצגת בדף התרגיל האפשרות להוסיף הערה אך ורק אם המשתמש הוא המנהל.
  3. אם אנחנו רוצים להציג בכל הדפים תפריט, אבל לא רוצים לשכפל את הקוד כשאנחנו יוצרים דף חדש עבור המערכת.

לצורך כך סביבות שמתעסקות באתרי אינטרנט לרוב מנגישות למתכנתים דבר שנקרא Web template engine.
הרעיון של Web template engines הוא לאפשר למתכנתים לכתוב HTML, ולהוסיף לו Syntax מוסכם מראש שיאפשר למתכנתים להטמיע בתוך אותו HTML תוכן דינאמי.

הנה, לדוגמה, קוד ב־Jinja2 (שנלקח מכאן). נניח שלקובץ קוראים user_list.html:

{% extends "layout.html" %}
{% block body %}
  <ul>
  {% for user in users %}
    <li><a href="{{ user.url }}">{{ user.username }}</a></li>
  {% endfor %}
  </ul>
{% endblock %}

בקוד הזה השורה הראשונה אומרת שה־HTML במסמך הזה הולך להיות “מודבק” בתוך מסמך אחר, שנקרא layout.html.
התוכן שבא אחר־כך מורה על ul (רשימה לא מסודרת של תבליטים) שמכילה תבליט אחד עבור כל משתמש במשתנה user.
עבור כל user מתוך רשימת users, יופיע קישור שיילקח מתוך user.url, והמלל של הקישור יהיה user.username.

שימו לב לשילוב המעניין בין תחביר פייתוני לבין תחביר של HTML. זו היכולת המעניינת (והמועילה) שאנחנו מקבלים ב־Web template engines בכלל, וב־Jinja2 בפרט.

התשתית שבה אנחנו משתמשים לבניית אתרים, Flask, משתמשת ב־Jinja2 בתור ה־Web template engine שמגיע יחד איתה.
כל קובץ שיהיה בתוך תיקיית templates בפרויקט שלנו נחשב ל"template", ו־Flask תדע לטעון אותו באמצעות הפונקציה render_template.
אם נניח נרצה לטעון את ה־template שנקרא user_list.html כאשר משתמש נכנס לעמוד /all_users, נכתוב את הקוד הבא:

@app.route('/all_users')
def login():
    registered_users = get_all_users()  # נניח שהפונקציה הזו מחזירה את רשימת המשתמשים במערכת
    render_template('user_list.html', users=registered_users)

ל־Jinja2 יש הרבה פיצ’רים וטריקים נחמדים.
אתם יכולים לקרוא עליהם בתיעוד, או לראות דוגמאות בקוד המקור של מערכת התרגילים.

אחסון באינטרנט

כשאנחנו רוצים להעלות אתר דינאמי שבנינו לאינטרנט, חשוב לנו שיקרו כמה דברים:

  1. שהוא יהיה נגיש מכל מקום בעולם, והניהול שלו יהיה נגיש מכל מקום בעולם.
  2. שהגישה אליו תהיה מהירה יחסית – זה אומר שהחיבור של המקום שמאחסן את האתר לאינטרנט יהיה טוב מאוד, ואולי אפילו הגורם המאחסן ישתמש בכל מני טריקים כדי להמהיר את הגישה לאתר שלנו.
  3. שיאפשר לנו לשלוט על המחשב שעליו מאוחסן האתר שלנו כאילו זה היה המחשב שלנו.

ישנם מספר שירותים שעונים על הדרישות האלו, ומוגדרים כפלטפורמה נוחה להעלות אליה את אתרי האינטרנט שתכנתנו באמצעות Flask (או באמצעות כל מודול/שפת תכנות אחרים).

השרת

למרות ש־Flask יודע לקבל בקשות HTTP, זו לא המומחיות שלו והוא מבצע את זה בצורה איטית וגרועה יחסית.
Flask מיועד בעיקר לספק לנו את הסביבה לפיתוח האתר, ופחות כרכיב שאחראי להיות השרת שמטפל בפניות HTTP.

לעומתו, ישנן תוכנות שייעודן לטפל בבקשות HTTP במהירות וביעילות. הן נקראות “שרתי אינטרנט” או “שרתי HTTP”, ולפעמים הן מגיעות עם פיצ’רים חשובים, כמו ניהול עומסים, תמיכה ב־IPv6, ניהול זיכרון מטמון, אפשרות להוספת headers לתשובות ששולח השרת, ניהול הקונפיגורציה של התקשורת המאובטחת (HTTPS) וכן הלאה.

שרתי האינטרנט המוכרים ביותר כיום הם nginx, Apache, lighttpd ו־IIS.
לצרכים שלנו הבחירה בינו לבין Apache או לבין lighttpd היא די שרירותית.

בשביל להשלים את הפאזל, נצטרך רכיב שידע לתרגם את מה שמגיע לשרת האינטרנט ל־Flask, ואת מה שיוצא מ־Flask לשרת האינטרנט. הרכיב הזה נקרא WSGI, או Web Server Gateway Interface. התוכנה שנבחר לצורך הזה נקראת Gunicorn.

לסיכום:

  1. יש לנו פייתון שמריץ Flask, שהוא מודול בפייתון שיודע לקבל בקשות לפי מיקום הבקשה (app.route) ולפיהן להחזיר תשובות.
  2. בד"כ נרצה לטעון בעזרתו templates שכתובים ב־Jinja2.
  3. את הבקשות מהאינטרנט תקבל תוכנה שזה הייעוד שלה ובזה היא טובה: שרת אינטרנט, כמו nginx או Apache.
  4. בין שרת האינטרנט ל־Flask יישב רכיב שיודע לתרגם דברים אחד לשני (WSGI). בחרנו באחד שנקרא Gunicorn.

ספק האחסון

כיום יש תחרות גדולה בקרב החברות הגדולות על מי “יזכה” לארח את האתר שלנו באינטרנט. בין החברות הגדולות שמציעות שירותים כאלו נמצאות Amazon, Goolge ו־Microsoft עם השירותים AWS, GCP ו־Azure (בהתאמה). החברות האלו מנסות לצוד בעיקר לקוחות ענק שישלמו להם אלפי דולרים ועד מיליוני דולרים בחודש על השכרת שרתים, אבל כדגי הרקק שאנחנו – רובן יציעו לנו תוכניות חינמיות משתלמות, לפחות לשנה בחינם.

ספק האחסון שנסקור הוא Heroku, שדואג לפשט את כל תהליך העלאת האתר. כדי להשתמש בו, תצטרכו לפתוח חשבון ב־Heroku.

צרו במחשב שלכם סביבה וירטואלית, כמו שלמדנו בתחילת היום. לצורך הדוגמה, נקרא לה heroku:

python -m venv heroku

הפעילו את הסביבה הוירטואלית באמצעות source heroku/bin/activate או heroku\Scripts\activate.bat, בתלות במערכת ההפעלה שלכם.
לאחר מכן התקינו את flask ואת gunicorn באמצעות pip.

צרו קובץ חדש ששמו app.py, וכיתבו בו קוד בסיסי שמשתמש ב־Flask:

from flask import Flask, request, jsonify
app = Flask(__name__)

@app.route('/')
def index():
    return "<h1>Welcome to our server !!</h1>"

if __name__ == '__main__':
    app.run(threaded=True, port=5000)

הריצו flask run, היכנסו ל־http://localhost:5000 ובדקו שהסביבה שלכם עובדת.

צרו את קובץ requirements.txt, שמכיל את כל התלויות ש־Heroku צריכים להכיר:

pip freeze > requirements.txt

צרו קובץ נוסף בשם Procfile (ללא סיומת).
הקובץ הזה הוא קובץ מיוחד עבור Heroku, שמגדיר עבור Heroku אילו פקודות להריץ בכל מני מקרים.
אנחנו נכתוב בתוכו רק את השורה:

web: gunicorn app:app

שאומרת: ברגע שמגיעה בקשת אינטרנט, תפנה אותה ל־gunicorn.
(מבולבלים? זה בסדר. Heroku דואג להרים Web server מאחורי הקלעים כדי שלא נצטרך להתעסק בזה. הוא יקבל את הבקשות ל־Web server שיעביר אותם ל־gunicorn).

עכשיו צרו git שינהל את כל הקוד שכתבתם – זה הדבר המנומס לעשות.
כתבו git init . בתיקייה הרלוונטית, ואז git add -A ו־git commit -a -m "First commit".

אנחנו כמעט מוכנים לשיגור. התחברו ל־Heroku, ולחצו על New -> Create new app.
צרו אפליקציה בשם שמתחשק לכם (שמרו אותו), וכשתגיעו לעמוד הניהול יהיו לכם שתי אפשרויות:

  1. העלאה באמצעות GitHub – אם העלתם את האפליקציה שלכם לגיטהאב זה מעולה, ותוכלו לעשות את זה ביתר קלות מכאן.
  2. העלאה באמצעות Heroku CLI – תצטרכו להתקין לפי המדריך כאן. אחרי שהתקנתם, כתבו heroku login בחלון חדש של שורת פקודה. אחרי שהתחברתם היכנסו לפרויקט שלכם, הפעילו את הסביבה הוירטואלית, כיתבו heroku git:remote -a PROJECT_NAME ואז git push heroku master.

עכשיו אתם אמורים להיות מסוגלים לראות את האתר שלכם ב־ https://PROJECT_NAME.herokuapp.com/. מזל טוב!

4 לייקים

שאלה ללייב של היום- יש אפשרות להדגים איך עובדים עם css בהקשר jinja ו-flask? למשל, אני משתמשת בFlaskForm כדי לייצר טופס, ויש בו כפתור submit. אבל משום מה כשאני מנסה לעצב בcss, הוא מקבל את העיצוב של ההורה (תגית form) ולא עיצוב שהגדרתי למחלקה שנתתי לו. תודה!

2 לייקים

היי.
כל נושא בסביבות הוירטואלית לא ממש יושב לי טוב.
אני עד עכשיו משתמש באנקונדה ופותח משם את הסביבת עבודה ויש לי מלא בלאגן עם זה. אשמח בלייב להבין רגע מאפס איך זה עובד בלי אנקונדה ועם אנקונדה הנושא הזה של סביבה וירטואלית כי לא ממש הצלחתי להתנתק מהבייס של האנקונדה

שאלה ללייב -
לגבי קלט של טקסט הבנתי כיצד לעבוד, מסקרן אותי לדעת כיצד עובדים כאשר מקבלים קובץ מהמשתמש לדוגמה תמונה, קבצי וידאו וכד’
האם יש צורת עבודה מקובלת? מהן הסכנות שטומנות בכך?
תודה

כדי שאתייחס אל זה בלייב אצטרך משהו יותר ספציפי.
קראת את הדוקומנטציה של עבודה עם קבצים ב¯Flask? מה שם לא יושב?

לא לגמרי הבנתי את הקשר בין אנקונדה, איך אתה משתמש באנקונדה והסביבה הוירטואלית. תוכל להרחיב בבקשה?

תוכלי לשתף בבקשה את ה¯CSS ואת התוצאה הויזואלית?

טרם קראתי, אבל קראתי שיש סכנות בקבלת קבצים מלקוח ותמיד צריך לחשוד במה המשתמש ינסה לעלות לשרת.
אם אפשר הכוונה בנושא של עבודה בצורה חכמה עם קבצים המתקבלים מלקוח אני אשמח (בפן האבטחתי)
ויכול להיות שהשאלה שלי עדיין בוסרית

ניתן להרחיב בלייב היום על הנושא של wsgi? דוגמא ספציפית תעזור מאוד.

זה קובץ הhtml של הטופס:


ספציפית מה שלא עובד זה form.submit עם מחלקת button.
זה הcss הרלוונטי:

זה העיצוב הסופי:

אפשר לראות פה שהכפתור Trump it לא מקבל את העיצוב שנתתי לו במחלקת buttom.

יש אפשרות לעלות תיקיות לgithub?
כי יש לי תיקייה בשם templates ש-flask חייבת לראות שהיא קיימת.

אני חושב שבגלל זה אני מקבל שגיאה.
אגב גם לא הצלחתי גם דרך התוכה של heroku הוא נתקע לי אחרי הlogin

כמובן שעל המחשב הכל עובד

מצטער שלא יכולתי להגיע לשיעור היום! חוגגים ירח דבש

3 לייקים

לא מצאתי API שמספק את כל המידע שרציתי בצורה מסודרת (ובכל מקרה רציתי גם להיות מסוגל לבצע חיפוש על כל המידע), אז השתמשתי ב webscraping בשביל לחלץ את כל המידע הדרוש וייצאתי אותו לקובץ json. האם נכון יותר לבצע את הפעולה הזו בכל פעם שהשרת עולה (אם זה בכלל אפשרי או הגיוני) או שמספיק לצרף את הקובץ כחלק מהקבצים לשרת ולאבד את היכולת לעדכן את המידע?

תלוי בצורך.
אם זה מידע שלא משתנה כמעט (נניח, רשימת הפוקימונים והמתקפות שלהם) – הגיוני לבדוק בהעלאת השרת האם הקובץ הזה קיים/מעודכן ולפעול בהתאם.
אם זה מידע שמתעדכן תדיר – אולי עדיף לעדכן את הנתונים בכל בקשה.

האיסוף של כל הנתונים לוקח בממוצע בין דקה וחצי ל-2 דקות, אז לעדכן אותו כל בקשה יהיה איום ונורא.
בגדול מדובר במידע שאכן כמעט ולא משתנה (אם בכלל), אז אלך עם התחושה הראשונית ואמשוך אותו בהעלאת השרת.

היי ים,

תודה על התייחסות בסרטוןן!

זה הלוג -
(אני חושב שחסרה לי תיקיית templates (בגלל flask) בגיטהאב ובגלל זה יש שגיאה, איך ניתן להקים תיקייה בגיטהאב?

פשוט צור קובץ index.html ב־templates

2 לייקים

תודה רבה!

2 לייקים

אני מנסה לדמיין את הסיטואציה שלך בירח דבש עם לפטופ :rofl: :rofl:
ים זה לגמרי נצחון שלך בכמה לוקחים את הקורס שלך ברצינות.

ועכשיו ברצינות - המון מזל טוב :smiley: :partying_face:

6 לייקים

הגעתי עד לפה, לא יכול להפסיד!

5 לייקים

איך עושים ירח דבש בתקופה כזאת?!
אני מניח שאתם בארץ… ?!
:sweat_smile::statue_of_liberty::statue_of_liberty: