نقل البيانات من App Engine Blobstore إلى Cloud Storage (الوحدة 16)

1. نظرة عامة

تهدف سلسلة البرامج التعليمية حول Serverless Migration Station (برامج تعليمية ذاتية السرعة وعملية) ومقاطع الفيديو ذات الصلة إلى مساعدة مطوّري الحوسبة بدون خادم على Google Cloud في تحديث تطبيقاتهم من خلال إرشادهم خلال عملية نقل واحدة أو أكثر، مع التركيز بشكل أساسي على التخلّي عن الخدمات القديمة. يؤدي ذلك إلى زيادة قابلية نقل تطبيقاتك ويمنحك المزيد من الخيارات والمرونة، ما يتيح لك الدمج مع مجموعة أكبر من منتجات Cloud والوصول إليها، كما يسهّل عليك الترقية إلى أحدث إصدارات اللغة. على الرغم من أنّ هذه السلسلة تركّز في البداية على أوائل مستخدمي Cloud، وخاصةً مطوّري App Engine (البيئة العادية)، إلا أنّها واسعة النطاق بما يكفي لتشمل منصات أخرى بلا خادم، مثل Cloud Functions وCloud Run، أو في أي مكان آخر إذا كان ذلك منطبقًا.

يعلّمك هذا الدرس التطبيقي حول الترميز كيفية نقل البيانات من App Engine Blobstore إلى Cloud Storage. تتوفّر أيضًا عمليات نقل ضمنية من:

راجِع أي وحدات نقل بيانات ذات صلة للحصول على مزيد من المعلومات المفصّلة.

ستتعرَّف على كيفية إجراء ما يلي:

  • إضافة استخدام واجهة برمجة التطبيقات/المكتبة App Engine Blobstore
  • تخزين عمليات تحميل المستخدمين في خدمة Blobstore
  • الاستعداد للخطوة التالية لنقل البيانات إلى Cloud Storage

المتطلبات

استطلاع

كيف ستستخدم هذا البرنامج التعليمي؟

قراءة المحتوى فقط قراءة المحتوى وإكمال التمارين

كيف تقيّم تجربتك مع Python؟

مبتدئ متوسط متمكّن

ما هو تقييمك لتجربة استخدام خدمات Google Cloud؟

مبتدئ متوسط متقدّم

2. الخلفية

يبدأ هذا الدرس التطبيقي حول الترميز باستخدام نموذج التطبيق من الوحدة 15 ويوضّح كيفية نقل البيانات من Blobstore (وNDB) إلى Cloud Storage (وCloud NDB). تتضمّن عملية نقل البيانات استبدال الاعتماديات على الخدمات المجمّعة القديمة في App Engine، ما يتيح لك نقل تطبيقاتك إلى منصة أخرى للحوسبة بدون خادم على السحابة الإلكترونية أو إلى منصة استضافة أخرى إذا أردت ذلك.

تتطلّب عملية النقل هذه جهدًا أكبر قليلاً مقارنةً بعمليات النقل الأخرى في هذه السلسلة. تعتمد Blobstore على إطار عمل تطبيق الويب الأصلي، وهذا هو السبب في أنّ نموذج التطبيق يستخدم إطار عمل webapp2 بدلاً من Flask. يتضمّن هذا البرنامج التعليمي عمليات نقل البيانات إلى Cloud Storage وCloud NDB وFlask وPython 3.

لا يزال التطبيق يسجّل "زيارات" المستخدمين النهائيين ويعرض آخر عشر زيارات، ولكن أضافت ورشة العمل البرمجية السابقة (الوحدة 15) وظيفة جديدة لاستيعاب استخدام Blobstore: يطلب التطبيق من المستخدمين النهائيين تحميل عنصر (ملف) يتوافق مع "زيارتهم". يمكن للمستخدمين إجراء ذلك أو النقر على "تخطّي" لإيقاف الميزة. بغض النظر عن قرار المستخدم، تعرض الصفحة التالية الناتج نفسه الذي عرضته الإصدارات السابقة من هذا التطبيق، أي أحدث الزيارات. هناك ميزة إضافية وهي أنّ الزيارات التي تتضمّن عناصر ذات صلة تعرض رابط "عرض" لعرض عنصر الزيارة. ينفّذ هذا الدرس التطبيقي حول الترميز عمليات النقل المذكورة سابقًا مع الحفاظ على الوظائف الموضّحة.

3- الإعداد/العمل التحضيري

قبل الانتقال إلى الجزء الرئيسي من البرنامج التعليمي، لنعدّ مشروعنا ونحصل على الرمز البرمجي، ثم ننشر التطبيق الأساسي لنعرف أنّنا بدأنا برمز برمجي يعمل.

1. إعداد المشروع

إذا سبق لك نشر تطبيق الوحدة التدريبية 15، ننصحك بإعادة استخدام المشروع (والرمز) نفسه. يمكنك بدلاً من ذلك إنشاء مشروع جديد تمامًا أو إعادة استخدام مشروع حالي آخر. تأكَّد من أنّ المشروع يتضمّن حساب فوترة نشطًا وأنّ خدمة App Engine مفعَّلة.

2. الحصول على نموذج تطبيق أساسي

من المتطلبات الأساسية لهذا الدرس التطبيقي حول الترميز توفُّر نموذج تطبيق يعمل من الوحدة 15. وإذا لم يكن لديك هذا النموذج، يمكنك الحصول عليه من مجلد "START" الخاص بالوحدة 15 (الرابط أدناه). يرشدك هذا الدرس العملي خلال كل خطوة، وينتهي برمز يشبه الرمز الموجود في المجلد "FINISH" في الوحدة 16.

يجب أن يبدو دليل ملفات بدء الوحدة التدريبية 15 على النحو التالي:

$ ls
README.md       app.yaml        main-gcs.py     main.py         templates

الملف main-gcs.py هو إصدار بديل من main.py من الوحدة 15 يتيح اختيار حزمة Cloud Storage تختلف عن الإعداد التلقائي لعنوان URL المخصّص للتطبيق استنادًا إلى رقم تعريف المشروع: PROJECT_ID.appspot.com. لا يؤدي هذا الملف أي دور في هذا الدرس التطبيقي حول الترميز (الوحدة 16) باستثناء أنّه يمكن تطبيق تقنيات نقل البيانات المشابهة على هذا الملف إذا أردت ذلك.

3- (إعادة) نشر التطبيق الأساسي

في ما يلي الخطوات المتبقية التي يجب تنفيذها الآن:

  1. التعرّف من جديد على أداة سطر الأوامر gcloud
  2. إعادة نشر نموذج التطبيق باستخدام gcloud app deploy
  3. تأكَّد من أنّ التطبيق يعمل على App Engine بدون أي مشاكل

بعد تنفيذ هذه الخطوات بنجاح وتأكيد أنّ تطبيق الوحدة 15 يعمل. تستقبل الصفحة الأولية المستخدمين بنموذج يطلب تحميل ملف عنصر زيارة مع خيار، وهو زر "تخطّي"، لإيقاف هذه الميزة:

f5b5f9f19d8ae978.png

بعد أن يحمّل المستخدمون ملفًا أو يتخطّوا هذه الخطوة، يعرض التطبيق صفحة "أحدث الزيارات" المألوفة:

f5ac6b98ee8a34cb.png

ستتضمّن الزيارات التي تتضمّن قطعة أثرية رابط "عرض" على يسار الطابع الزمني للزيارة لعرض القطعة الأثرية (أو تنزيلها). بعد التأكّد من وظائف التطبيق، ستكون جاهزًا لنقل البيانات من خدمات App Engine القديمة (webapp2 وNDB وBlobstore) إلى البدائل الحديثة (Flask وCloud NDB وCloud Storage).

4. تعديل ملفات الإعداد

تتضمّن النسخة المعدَّلة من تطبيقنا ثلاثة ملفات إعداد، والمهام المطلوبة هي:

  1. تعديل مكتبات الجهات الخارجية المضمّنة المطلوبة في app.yaml بالإضافة إلى إتاحة إمكانية نقل البيانات إلى Python 3
  2. أضِف requirements.txt، الذي يحدّد جميع المكتبات المطلوبة غير المضمّنة
  3. أضِف appengine_config.py لكي يتوافق التطبيق مع المكتبات المضمَّنة وغير المضمَّنة التابعة لجهات خارجية

app.yaml

عدِّل ملف app.yaml من خلال تعديل القسم libraries. إزالة jinja2 وإضافة grpcio وsetuptools وssl اختَر أحدث إصدار متاح لجميع المكتبات الثلاث. أضِف أيضًا توجيه Python 3 runtime، ولكن مع إيقاف التعليق عليه. عند الانتهاء، من المفترض أن يظهر على النحو التالي (في حال اختيار Python 3.9):

قبل:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: jinja2
  version: latest

بعد:

#runtime: python39
runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: grpcio
  version: latest
- name: setuptools
  version: latest
- name: ssl
  version: latest

تتعامل التغييرات بشكل أساسي مع مكتبات Python 2 المضمّنة المتوفّرة على خوادم App Engine (لذلك لن تحتاج إلى تجميعها بنفسك). أزلنا Jinja2 لأنّه يأتي مع Flask، وسنضيف Flask إلى ملف reqs.txt. عند استخدام مكتبات برامج Google Cloud، مثل تلك الخاصة بخدمتَي Cloud NDB وCloud Storage، يجب توفُّر grpcio وsetuptools. أخيرًا، يتطلّب Cloud Storage مكتبة SSL. يتم استخدام توجيه وقت التشغيل الذي تم التعليق عليه في الأعلى عندما تكون مستعدًا لنقل هذا التطبيق إلى Python 3. سنتناول هذا الموضوع في نهاية هذا البرنامج التعليمي.

requirements.txt

أضِف ملف requirements.txt يتطلّب إطار عمل Flask ومكتبتَي برامج Cloud NDB وCloud Storage، وكلّها غير مضمّنة. أنشئ الملف بالمحتوى التالي:

flask
google-cloud-ndb
google-cloud-storage

يتطلّب وقت تشغيل Python 2 في App Engine تجميع مكتبات الجهات الخارجية غير المضمّنة ذاتيًا، لذا نفِّذ الأمر التالي لتثبيت هذه المكتبات في مجلد lib:

pip install -t lib -r requirements.txt

إذا كان لديك الإصداران 2 و3 من Python على جهاز التطوير، قد تحتاج إلى استخدام الأمر pip2 لضمان الحصول على إصدارات Python 2 من هذه المكتبات. بعد الترقية إلى Python 3، لن تحتاج إلى تجميع الحزم بنفسك.

appengine_config.py

أضِف ملف appengine_config.py يتوافق مع المكتبات التابعة لجهات خارجية والمضمّنة وغير المضمّنة. أنشئ الملف بالمحتوى التالي:

import pkg_resources
from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)
# Add libraries to pkg_resources working set to find the distribution.
pkg_resources.working_set.add_entry(PATH)

يجب أن تكون الخطوات التي تم إكمالها للتو مشابهة أو مطابقة للخطوات المُدرَجة في قسم تثبيت المكتبات لتطبيقات Python 2 في مستندات App Engine، وبشكل أكثر تحديدًا، يجب أن تتطابق محتويات appengine_config.py مع ما هو وارد في الخطوة 5 هناك.

اكتمل العمل على ملفات الإعداد، لذا لننتقل إلى التطبيق.

5- تعديل ملفات التطبيق

عمليات الاستيراد

تتضمّن المجموعة الأولى من التغييرات على main.py استبدال كل العناصر التي سيتم تغييرها. في ما يلي التغييرات التي سيتم إجراؤها:

  1. تم استبدال webapp2 بـ Flask
  2. بدلاً من استخدام Jinja2 من webapp2_extras، استخدِم Jinja2 المضمّن في Flask
  3. تم استبدال App Engine Blobstore وNDB بـ Cloud NDB وCloud Storage
  4. تم استبدال معالجات Blobstore في webapp بمجموعة من وحدة المكتبة العادية io وFlask وأدوات werkzeug
  5. تكتب Blobstore تلقائيًا في حزمة Cloud Storage تحمل اسم عنوان URL لتطبيقك (PROJECT_ID.appspot.com). وبما أنّنا نكيّف البرنامج مع مكتبة برامج Cloud Storage، يتم استخدام google.auth للحصول على رقم تعريف المشروع لتحديد اسم الحزمة نفسه بالضبط. (يمكنك تغيير اسم الحزمة لأنّه لم يعُد مبرمَجًا بشكل ثابت).

قبل:

import webapp2
from webapp2_extras import jinja2
from google.appengine.ext import blobstore, ndb
from google.appengine.ext.webapp import blobstore_handlers

طبِّق التغييرات الواردة في القائمة أعلاه عن طريق استبدال قسم الاستيراد الحالي في main.py بمقتطف الرمز البرمجي أدناه.

بعد:

import io

from flask import (Flask, abort, redirect, render_template,
        request, send_file, url_for)
from werkzeug.utils import secure_filename

import google.auth
from google.cloud import exceptions, ndb, storage

عملية الإعداد وإتاحة استخدام Jinja2 بدون داعٍ

مجموعة الرموز التالية التي يجب استبدالها هي BaseHandler التي تحدّد استخدام Jinja2 من webapp2_extras. هذا الإجراء غير ضروري لأنّ Jinja2 يأتي مع Flask وهو محرك إنشاء النماذج التلقائي، لذا عليك إزالته.

في الوحدة 16، أنشئ مثيلات للكائنات التي لم تكن متوفرة في التطبيق القديم، ويشمل ذلك تهيئة تطبيق Flask وإنشاء برامج API لخدمتَي Cloud NDB وCloud Storage. أخيرًا، نجمع اسم حزمة Cloud Storage كما هو موضّح أعلاه في قسم عمليات الاستيراد. في ما يلي مثال على ذلك قبل تنفيذ هذه التعديلات وبعده:

قبل:

class BaseHandler(webapp2.RequestHandler):
    'Derived request handler mixing-in Jinja2 support'
    @webapp2.cached_property
    def jinja2(self):
        return jinja2.get_jinja2(app=self.app)

    def render_response(self, _template, **context):
        self.response.write(self.jinja2.render_template(_template, **context))

بعد:

app = Flask(__name__)
ds_client = ndb.Client()
gcs_client = storage.Client()
_, PROJECT_ID = google.auth.default()
BUCKET = '%s.appspot.com' % PROJECT_ID

تعديل إذن الوصول إلى Datastore

يتوافق Cloud NDB إلى حد كبير مع App Engine NDB. أحد الاختلافات التي تم تناولها سابقًا هو الحاجة إلى برنامج عميل لواجهة برمجة التطبيقات. أما الاختلاف الآخر فهو أنّ الأخير يتطلّب التحكّم في الوصول إلى Datastore من خلال أداة إدارة السياق في Python الخاصة بعميل واجهة برمجة التطبيقات. يعني ذلك بشكل أساسي أنّه لا يمكن إجراء جميع طلبات الوصول إلى Datastore باستخدام مكتبة برامج Cloud NDB إلا ضمن حظر with في Python.

هذا هو التغيير الأول، أما التغيير الثاني فهو أنّ Blobstore والعناصر التابعة له، مثل BlobKey، غير متوافقة مع Cloud Storage، لذا يجب تغيير file_blob إلى ndb.StringProperty بدلاً من ذلك. في ما يلي فئة نموذج البيانات والدالتان store_visit() وfetch_visits() المعدّلتان اللتان تعكسان هذه التغييرات:

قبل:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)
    file_blob = ndb.BlobKeyProperty()

def store_visit(remote_addr, user_agent, upload_key):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent),
            file_blob=upload_key).put()

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)

بعد:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)
    file_blob = ndb.StringProperty()

def store_visit(remote_addr, user_agent, upload_key):
    'create new Visit entity in Datastore'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent),
                file_blob=upload_key).put()

def fetch_visits(limit):
    'get most recent visits'
    with ds_client.context():
        return Visit.query().order(-Visit.timestamp).fetch(limit)

في ما يلي تمثيل صوري للتغييرات التي تم إجراؤها حتى الآن:

a8f74ca392275822.png

تعديل المعالجات

معالج التحميل

المعالِجات في webapp2 هي فئات، بينما هي دوال في Flask. بدلاً من طريقة فعل HTTP، يستخدم Flask الفعل لتزيين الدالة. يتم استبدال Blobstore ومعالجات webapp بوظائف من Cloud Storage بالإضافة إلى Flask وأدواتها المساعدة:

قبل:

class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
    'Upload blob (POST) handler'
    def post(self):
        uploads = self.get_uploads()
        blob_id = uploads[0].key() if uploads else None
        store_visit(self.request.remote_addr, self.request.user_agent, blob_id)
        self.redirect('/', code=307)

بعد:

@app.route('/upload', methods=['POST'])
def upload():
    'Upload blob (POST) handler'
    fname = None
    upload = request.files.get('file', None)
    if upload:
        fname = secure_filename(upload.filename)
        blob = gcs_client.bucket(BUCKET).blob(fname)
        blob.upload_from_file(upload, content_type=upload.content_type)
    store_visit(request.remote_addr, request.user_agent, fname)
    return redirect(url_for('root'), code=307)

في ما يلي بعض الملاحظات حول هذا التعديل:

  • بدلاً من blob_id، يتم الآن تحديد عناصر الملفات باستخدام اسم الملف (fname) إذا كان متوفّرًا، وإلا يتم استخدام None (إذا اختار المستخدم عدم تحميل ملف).
  • لقد أزالت معالجات Blobstore عملية التحميل من مستخدميها، ولكنّ Cloud Storage لم تفعل ذلك، لذا يمكنك الاطّلاع على الرمز الذي تمت إضافته حديثًا والذي يضبط عنصر blob الخاص بالملف وموقعه (الحزمة) بالإضافة إلى الطلب الذي ينفّذ عملية التحميل الفعلية. (upload_from_file()).
  • تستخدم webapp2 جدول توجيه في أسفل ملف التطبيق، بينما يتم العثور على مسارات Flask في كل معالج مزخرف.
  • يختتم كلا المعالجَين وظائفهما بإعادة التوجيه إلى الصفحة الرئيسية ( / ) مع الحفاظ على طلب POST باستخدام رمز الإرجاع HTTP 307.

معالج التنزيل

تتّبع عملية تعديل أداة معالجة التنزيل نمطًا مشابهًا لنمط أداة معالجة التحميل، ولكنّ الرمز الذي يجب الاطّلاع عليه أقل بكثير. استبدِل وظائف Blobstore وwebapp بوظائف Cloud Storage وFlask المكافئة:

قبل:

class ViewBlobHandler(blobstore_handlers.BlobstoreDownloadHandler):
    'view uploaded blob (GET) handler'
    def get(self, blob_key):
        self.send_blob(blob_key) if blobstore.get(blob_key) else self.error(404)

بعد:

@app.route('/view/<path:fname>')
def view(fname):
    'view uploaded blob (GET) handler'
    blob = gcs_client.bucket(BUCKET).blob(fname)
    try:
        media = blob.download_as_bytes()
    except exceptions.NotFound:
        abort(404)
    return send_file(io.BytesIO(media), mimetype=blob.content_type)

ملاحظات حول هذا التحديث:

  • مرة أخرى، يزيّن Flask دوال المعالجة بمسارها، بينما يفعل webapp ذلك في جدول توجيه في الأسفل، لذا عليك التعرّف على بنية مطابقة الأنماط في ('/view/([^/]+)?') مقابل بنية Flask ('/view/<path:fname>').
  • كما هو الحال مع معالج التحميل، هناك بعض العمل الإضافي المطلوب على جانب Cloud Storage للوظائف التي تم تجريدها من خلال معالجات Blobstore، أي تحديد الملف (الكائن الثنائي الكبير) المعني وتنزيل الملف الثنائي بشكل صريح مقابل استدعاء طريقة send_blob() الفردية لمعالج Blobstore.
  • في كلتا الحالتين، يتم عرض خطأ HTTP 404 للمستخدم إذا لم يتم العثور على عنصر.

المعالج الرئيسي

تحدث التغييرات النهائية على التطبيق الرئيسي في المعالج الرئيسي. تم استبدال طرق أفعال HTTP webapp2 بدالة واحدة تجمع وظائفها. استبدِل الفئة MainHandler بالدالة root() وأزِل جدول التوجيه webapp2 كما هو موضّح أدناه:

قبل:

class MainHandler(BaseHandler):
    'main application (GET/POST) handler'
    def get(self):
        self.render_response('index.html',
                upload_url=blobstore.create_upload_url('/upload'))

    def post(self):
        visits = fetch_visits(10)
        self.render_response('index.html', visits=visits)

app = webapp2.WSGIApplication([
    ('/', MainHandler),
    ('/upload', UploadHandler),
    ('/view/([^/]+)?', ViewBlobHandler),
], debug=True)

بعد:

@app.route('/', methods=['GET', 'POST'])
def root():
    'main application (GET/POST) handler'
    context = {}
    if request.method == 'GET':
        context['upload_url'] = url_for('upload')
    else:
        context['visits'] = fetch_visits(10)
    return render_template('index.html', **context)

بدلاً من طريقتَي get() وpost() المنفصلتَين، هما في الأساس عبارة if-else في root(). بالإضافة إلى ذلك، بما أنّ root() هي دالة واحدة، لا يتم استدعاؤها إلا مرة واحدة لعرض النموذج لكل من GET وPOST، في حين أنّ ذلك غير ممكن في webapp2.

في ما يلي تمثيل صوري لهذه المجموعة الثانية والأخيرة من التغييرات التي ستسري اعتبارًا من ‎main.py:

5ec38818c32fec2.png

(اختياري) "تحسين" التوافق مع الإصدارات السابقة

إذًا، يعمل الحلّ المذكور أعلاه بشكلٍ مثالي، ولكن فقط إذا كنت تبدأ من الصفر ولم تنشئ ملفات باستخدام Blobstore. بما أنّنا عدّلنا التطبيق لتحديد الملفات حسب اسم الملف بدلاً من BlobKey، لن يتمكّن تطبيق الوحدة التدريبية 16 المكتمل كما هو من عرض ملفات Blobstore. بعبارة أخرى، أجرينا تغييرًا غير متوافق مع الإصدارات السابقة عند تنفيذ عملية نقل البيانات هذه. نقدّم الآن إصدارًا بديلاً من main.py يُسمى main-migrate.py (يمكن العثور عليه في المستودع) يحاول سدّ هذه الفجوة.

أول "امتداد" لدعم الملفات التي تم إنشاؤها باستخدام Blobstore هو نموذج بيانات يحتوي على BlobKeyProperty (بالإضافة إلى StringProperty للملفات التي تم إنشاؤها باستخدام Cloud Storage):

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)
    file_blob = ndb.BlobKeyProperty()  # backwards-compatibility
    file_gcs  = ndb.StringProperty()

سيتم استخدام السمة file_blob لتحديد الملفات التي تم إنشاؤها باستخدام Blobstore، بينما تُستخدَم السمة file_gcs لملفات Cloud Storage. عند إنشاء زيارات جديدة، يتم الآن تخزين قيمة بشكلٍ صريح في file_gcs بدلاً من file_blob، لذا يبدو store_visit مختلفًا قليلاً:

قبل:

def store_visit(remote_addr, user_agent, upload_key):
    'create new Visit entity in Datastore'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent),
                file_blob=upload_key).put()

بعد:

def store_visit(remote_addr, user_agent, upload_key):
    'create new Visit entity in Datastore'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent),
                file_gcs=upload_key).put()

عند جلب أحدث الزيارات، يجب "تسوية" البيانات قبل إرسالها إلى النموذج:

قبل:

@app.route('/', methods=['GET', 'POST'])
def root():
    'main application (GET/POST) handler'
    context = {}
    if request.method == 'GET':
        context['upload_url'] = url_for('upload')
    else:
        context['visits'] = fetch_visits(10)
    return render_template('index.html', **context)

بعد:

@app.route('/', methods=['GET', 'POST'])
def root():
    'main application (GET/POST) handler'
    context = {}
    if request.method == 'GET':
        context['upload_url'] = url_for('upload')
    else:
        context['visits'] = etl_visits(fetch_visits(10))
    return render_template('index.html', **context)

بعد ذلك، أكِّد ما إذا كان الرمز file_blob أو file_gcs (أو لا شيء منهما) متوفّرًا. إذا كان هناك ملف متاح، اختَر الملف الحالي واستخدِم المعرّف (BlobKey للملفات التي تم إنشاؤها باستخدام Blobstore أو اسم الملف للملفات التي تم إنشاؤها باستخدام Cloud Storage). عندما نقول "الملفات التي تم إنشاؤها باستخدام Cloud Storage"، نعني الملفات التي تم إنشاؤها باستخدام مكتبة برامج Cloud Storage. تكتب Blobstore أيضًا في Cloud Storage، ولكن في هذه الحالة، ستكون هذه الملفات من إنشاء Blobstore.

والآن، الأهم من ذلك، ما هي وظيفة etl_visits() المستخدَمة لتسوية البيانات أو استخراجها وتحويلها وتحميلها (ETL) للمستخدم النهائي؟ يبدو على النحو التالي:

def etl_visits(visits):
    return [{
            'visitor': v.visitor,
            'timestamp': v.timestamp,
            'file_blob': v.file_gcs if hasattr(v, 'file_gcs') \
                    and v.file_gcs else v.file_blob
            } for v in visits]

من المحتمل أن يبدو كما توقعت: تتكرّر التعليمات البرمجية خلال جميع الزيارات، وفي كل زيارة، يتم أخذ بيانات الزائر والطابع الزمني كما هي، ثم يتم التحقّق مما إذا كان file_gcs أو file_blob متوفّرًا، وإذا كان الأمر كذلك، يتم اختيار أحدهما (أو None إذا لم يكن أي منهما متوفّرًا).

في ما يلي توضيح للاختلافات بين main.py وmain-migrate.py:

718b05b2adadb2e1.png

إذا كنت تبدأ من الصفر بدون ملفات تم إنشاؤها باستخدام Blobstore، استخدِم main.py، ولكن إذا كنت بصدد الانتقال وتريد دعم الملفات التي تم إنشاؤها باستخدام كل من Blobstore و Cloud Storage، اطّلِع على main-migrate.py كمثال على كيفية التعامل مع سيناريو مثل هذا لمساعدتك في التخطيط لعمليات نقل البيانات لتطبيقاتك. عند إجراء عمليات نقل معقّدة، من المحتمل أن تنشأ حالات خاصة، لذا يهدف هذا المثال إلى إظهار ميل أكبر نحو تحديث التطبيقات الحقيقية باستخدام بيانات حقيقية.

6. الملخّص/التنظيف

يختتم هذا القسم الدرس التطبيقي حول الترميز من خلال تفعيل التطبيق والتأكّد من عمله على النحو المطلوب وفي أي ناتج معروض. بعد التحقّق من التطبيق، اتّخِذ أي خطوات تنظيف وفكِّر في الخطوات التالية.

نشر التطبيق والتحقّق منه

قبل إعادة نشر تطبيقك، احرص على تشغيل pip install -t lib -r requirements.txt للحصول على المكتبات التابعة لجهات خارجية والمجمّعة ذاتيًا في مجلد lib. إذا أردت تشغيل الحلّ المتوافق مع الإصدارات القديمة، أعِد تسمية main-migrate.py إلى main.py أولاً. الآن، شغِّل gcloud app deploy وتأكَّد من أنّ التطبيق يعمل بشكل مطابق لتطبيق الوحدة التدريبية 15. تبدو شاشة النموذج كما يلي:

f5b5f9f19d8ae978.png

تبدو صفحة الزيارات الأخيرة على النحو التالي:

f5ac6b98ee8a34cb.png

تهانينا على إكمال هذا الدرس التطبيقي حول الترميز الذي يهدف إلى استبدال App Engine Blobstore بـ Cloud Storage، وApp Engine NDB بـ Cloud NDB، وwebapp2 بـ Flask. يجب أن يتطابق الرمز الآن مع ما هو موجود في مجلد FINISH (الوحدة 16). يتوفّر البديل main-migrate.py أيضًا في هذا المجلد.

"نقل" Python 3

إنّ توجيه runtime Python 3 الذي تم التعليق عليه في أعلى app.yaml هو كل ما يلزم لنقل هذا التطبيق إلى Python 3. رمز المصدر نفسه متوافق مع Python 3، لذا لا يلزم إجراء أي تغييرات عليه. لتنفيذ ذلك كتطبيق Python 3، اتّبِع الخطوات التالية:

  1. أزِل التعليق من توجيه runtime في Python 3 في أعلى app.yaml.
  2. احذف جميع الأسطر الأخرى في app.yaml.
  3. احذف الملف appengine_config.py. (غير مستخدَم في وقت تشغيل Python 3)
  4. احذف المجلد lib إذا كان متوفّرًا. (غير ضروري مع وقت تشغيل Python 3)

تَنظيم

للجمهور العام

إذا انتهيت من استخدام التطبيق في الوقت الحالي، ننصحك بإيقاف تطبيق App Engine لتجنُّب تحمّل رسوم. ومع ذلك، إذا أردت إجراء المزيد من الاختبارات أو التجارب، تتضمّن منصة App Engine حصّة مجانية، وبالتالي لن يتم تحصيل رسوم منك طالما أنّك لا تتجاوز مستوى الاستخدام هذا. هذا السعر خاص بالحوسبة، ولكن قد تكون هناك رسوم أيضًا مقابل خدمات App Engine ذات الصلة، لذا يُرجى الاطّلاع على صفحة الأسعار للحصول على مزيد من المعلومات. إذا كان نقل البيانات هذا يتضمّن خدمات سحابية أخرى، يتم إصدار فواتير لها بشكل منفصل. في كلتا الحالتين، إذا كان ذلك منطبقًا، راجِع القسم "خاص بهذا الدرس التطبيقي حول الترميز" أدناه.

للتوضيح، يؤدي النشر على منصة حوسبة بدون خادم في Google Cloud، مثل App Engine، إلى تكبُّد تكاليف بسيطة للإنشاء والتخزين. تتضمّن Cloud Build حصة مجانية خاصة بها، وكذلك Cloud Storage. ويؤدي تخزين هذه الصورة إلى استهلاك جزء من هذه الحصة. ومع ذلك، قد تكون مقيمًا في منطقة لا تتوفّر فيها هذه الطبقة المجانية، لذا عليك الانتباه إلى استخدامك لمساحة التخزين لتقليل التكاليف المحتملة. تتضمّن "المجلدات" المحدّدة في Cloud Storage التي يجب مراجعتها ما يلي:

  • console.cloud.google.com/storage/browser/LOC.artifacts.PROJECT_ID.appspot.com/containers/images
  • console.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com
  • تعتمد روابط مساحة التخزين أعلاه على PROJECT_ID و *LOC*، على سبيل المثال، "us" إذا كان تطبيقك مستضافًا في الولايات المتحدة.

من ناحية أخرى، إذا كنت لن تواصل استخدام هذا التطبيق أو غيره من الدروس التعليمية البرمجية المتعلقة بنقل البيانات وأردت حذف كل شيء تمامًا، عليك إيقاف مشروعك.

خاص بهذا الدرس التطبيقي حول الترميز

الخدمات المدرَجة أدناه خاصة بهذا الدرس التطبيقي حول الترميز. يُرجى الرجوع إلى مستندات كل منتج للحصول على مزيد من المعلومات:

يُرجى العِلم أنّه في حال نقل البيانات من الوحدة 15 إلى الوحدة 16، ستظلّ البيانات متوفّرة في Blobstore، وهذا هو السبب في تضمين معلومات الأسعار أعلاه.

الخطوات التالية

بالإضافة إلى هذا البرنامج التعليمي، تتضمّن وحدات نقل البيانات الأخرى التي تركّز على التوقّف عن استخدام الخدمات المجمّعة القديمة ما يلي:

  • الوحدة 2: نقل البيانات من App Engine ndb إلى Cloud NDB
  • الوحدات من 7 إلى 9: نقل المهام من مهام الدفع في "قائمة انتظار المهام" في App Engine إلى Cloud Tasks
  • الوحدتان 12 و13: نقل البيانات من App Engine Memcache إلى Cloud Memorystore
  • الوحدتان 18 و19: نقل البيانات من "قائمة انتظار المهام" في App Engine (مهام السحب) إلى Cloud Pub/Sub

لم تعُد App Engine هي المنصة الوحيدة بدون خادم في Google Cloud. إذا كان لديك تطبيق صغير على App Engine أو تطبيق بوظائف محدودة وتريد تحويله إلى خدمة مصغّرة مستقلة، أو إذا كنت تريد تقسيم تطبيق متكامل إلى عدة مكونات قابلة لإعادة الاستخدام، ستكون هذه أسبابًا وجيهة للنقل إلى Cloud Functions. إذا أصبحت عملية إنشاء الحاويات جزءًا من سير عمل تطوير التطبيقات، خاصةً إذا كانت تتألف من مسار CI/CD (التكامل المستمر/التسليم المتواصل أو النشر المستمر)، ننصحك بالانتقال إلى Cloud Run. تتناول الوحدات التدريبية التالية هذه السيناريوهات:

  • نقل البيانات من App Engine إلى Cloud Functions: يمكنك الاطّلاع على الوحدة 11
  • الانتقال من App Engine إلى Cloud Run: يمكنك الاطّلاع على الوحدة 4 لتضمين تطبيقك في حاوية باستخدام Docker، أو الوحدة 5 لإجراء ذلك بدون حاويات أو معرفة بـ Docker أو Dockerfile

إنّ الانتقال إلى منصة أخرى بلا خادم هو أمر اختياري، وننصحك بمراجعة أفضل الخيارات لتطبيقاتك وحالات الاستخدام قبل إجراء أي تغييرات.

بغض النظر عن وحدة النقل التي ستختارها، يمكنك الوصول إلى كل محتوى Serverless Migration Station (الدروس البرمجية والفيديوهات ورمز المصدر [عند توفّره]) من خلال مستودع المصدر المفتوح. يوفّر مستودع README أيضًا إرشادات حول عمليات نقل البيانات التي يجب أخذها في الاعتبار وأي "ترتيب" ذي صلة لوحدات نقل البيانات.

7. مراجع إضافية

مشاكل/ملاحظات بشأن الدروس التطبيقية حول الترميز

إذا واجهت أي مشاكل في هذا الدرس العملي، يُرجى البحث عن مشكلتك أولاً قبل إرسالها. روابط للبحث عن مشاكل جديدة وإنشائها:

مراجع لنقل البيانات

يمكنك العثور على روابط لمجلدات المستودع الخاصة بالوحدة التدريبية 15 (البداية) والوحدة التدريبية 16 (النهاية) في الجدول أدناه. يمكن أيضًا الوصول إليها من مستودع جميع عمليات نقل Codelab في App Engine الذي يمكنك استنساخه أو تنزيل ملف ZIP منه.

Codelab

Python 2

Python 3

الوحدة 15

code

لا ينطبق

الوحدة 16 (هذا الدرس التطبيقي حول الترميز)

code

(كما هو الحال في الإصدار 2 من Python)

مراجع متوفرة على الإنترنت

في ما يلي مراجع على الإنترنت قد تكون ذات صلة بهذا البرنامج التعليمي:

‫App Engine Blobstore وCloud Storage

النظام الأساسي لـ App Engine

معلومات أخرى حول السحابة الإلكترونية

Python

الفيديوهات

الترخيص

يخضع هذا العمل لترخيص المشاع الإبداعي مع نسب العمل إلى مؤلفه 2.0 Generic License.