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

1. نظرة عامة

تهدف سلسلة الدروس التطبيقية حول الترميز بدون خادم (البرامج التعليمية العملية) والفيديوهات ذات الصلة إلى مساعدة مطوّري 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، والتي تتيح لك نقل تطبيقاتك إلى نظام أساسي آخر على Cloud بدون خادم أو نظام أساسي آخر للاستضافة إذا كنت ترغب في ذلك.

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

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

3- الإعداد/التمهيد

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

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

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

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

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

يجب أن يبدو دليل ملفات البدء في الوحدة 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. اختر أحدث إصدار متاح للمكتبات الثلاث جميعها. أضِف أيضًا التوجيه runtime في Python 3، ولكن أدرَجنا تعليقًا. عند الانتهاء، سيبدو على النحو التالي (إذا حددت 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، وسنضيفه إلى 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

إذا كنت تستخدم Python 2 و3 على جهاز التطوير، فقد تضطر إلى استخدام الأمر 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" بـ "قارورة".
  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 وإنشاء برامج واجهة برمجة تطبيقات لكلّ من 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

تعديل إذن الوصول إلى مخزن البيانات

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

هذا تغيير واحد؛ والأخرى هي أن 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 وFlood:

قبل:

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، وتحديدًا تحديد الملف (blob) المعني وتنزيل طلب الطريقة 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 (Module 16). يتوفّر main-migrate.py البديل أيضًا في هذا المجلد.

"نقل البيانات" في لغة Python 3

كل ما تحتاج إليه هو توجيه Python 3 runtime الذي تم التعليق عليه في أعلى 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 ذات الصلة، لذا يُرجى التحقق من صفحة الأسعار للحصول على مزيد من المعلومات. وإذا كانت عملية النقل هذه تشمل خدمات Cloud أخرى، يتم تحصيل الرسوم منها بشكل منفصل. وفي كلتا الحالتين، يمكنك الاطّلاع على قسم "الدروس التطبيقية حول الترميز" إذا كان ذلك منطبقًا. أدناه.

للإفصاح الكامل عن المعلومات، يتحمّل النشر على منصة حوسبة بدون خادم في 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 إلى "مهام السحابة الإلكترونية"
  • الوحدات 12-13: النقل من App Engine Memcache إلى Cloud Memorystore
  • الوحدات 18-19: النقل من قائمة انتظار مهام App Engine (سحب المهام) إلى Cloud Pub/Sub

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

  • نقل البيانات من App Engine إلى Cloud Functions: راجِع الوحدة 11.
  • نقل البيانات من App Engine إلى Cloud Run: راجِع الوحدة 4 لتضمين تطبيقك مع Docker، أو الوحدة 5 لتنفيذ ذلك بدون حاويات أو معلومات Docker أو Dockerfiles.

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

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

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

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

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

موارد نقل البيانات

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

Codelab

Python 2

Python 3

الوحدة 15

الرموز البرمجية

لا ينطبق

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

الرموز البرمجية

(مثل Python 2)

مراجع على الإنترنت

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

التخزين في السحابة الإلكترونية وApp Engine Blobstore

منصة App Engine

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

Python

الفيديوهات

الترخيص

هذا العمل مرخّص بموجب رخصة المشاع الإبداعي 2.0 مع نسب العمل إلى مؤلف عام.