App Engine blobstore를 Cloud Storage로 마이그레이션

이 가이드에서는 App Engine Blobstore에서 Cloud Storage로 마이그레이션하는 방법을 설명합니다.

Cloud Storage는 동영상 또는 이미지 파일과 같은 대용량 데이터 객체(blob)를 처리하고 사용자가 큰 데이터 파일을 업로드할 수 있다는 점에서 App Engine Blobstore와 유사합니다. App Engine 기존 번들 서비스를 통해서만 App Engine Blobstore에 액세스할 수 있지만 Cloud Storage는 Cloud 클라이언트 라이브러리를 통해 액세스할 수 있는 독립형 Google Cloud 제품입니다. Cloud Storage는 앱에 최신 객체 스토리지 솔루션을 제공하고 나중에 유연하게 Cloud Run 또는 다른 Google Cloud 앱 호스팅 플랫폼으로 마이그레이션할 수 있도록 지원합니다.

2016년 11월 이후에 생성된 Google Cloud 프로젝트의 경우 Blobstore는 백그라운드에서 Cloud Storage 버킷을 사용합니다. 즉, 앱을 Cloud Storage로 마이그레이션할 때 기존 Cloud Storage 버킷의 모든 기존 객체와 권한은 변경되지 않습니다. Cloud Storage용 Cloud 클라이언트 라이브러리를 사용하여 이러한 기존 버킷에 액세스할 수도 있습니다.

주요 차이점 및 유사점

Cloud Storage는 다음 Blobstore 종속 항목과 제한사항을 제외합니다.

  • Python 2용 Blobstore API는 웹앱에 종속됩니다.
  • Python 3용 Blobstore API는 유틸리티 클래스를 사용하여 Blobstore 핸들러를 사용합니다.
  • Blobstore의 경우 Blobstore에 업로드할 수 있는 최대 파일 수는 500개입니다. Cloud Storage 버킷에서 만들 수 있는 객체 수에는 제한이 없습니다.

Cloud Storage는 다음을 지원하지 않습니다.

  • Blobstore 핸들러 클래스
  • Blobstore 객체

Cloud Storage와 App Engine Blobstore의 유사점은 다음과 같습니다.

  • 런타임 환경에서 대용량 데이터 객체를 읽고 쓸 수 있을 뿐만 아니라 영화, 이미지 또는 기타 정적 콘텐츠와 같은 큰 정적 데이터 객체를 저장하고 제공할 수 있습니다. Cloud Storage의 객체 크기 한도는 5TiB입니다.
  • Cloud Storage 버킷에 객체를 저장할 수 있습니다.
  • 무료 등급이 있습니다.

시작하기 전에

  • Cloud Storage 가격 책정과 할당량을 검토하고 이해해야 합니다.
  • Blobstore를 사용하는 기존 Python 2 또는 Python 3 App Engine 앱이 있습니다.
  • 이 가이드의 예시에서는 Flask 프레임워크를 사용하여 Cloud Storage로 마이그레이션하는 앱을 보여줍니다. Cloud Storage로 마이그레이션할 때 webapp2 유지를 포함하여 모든 웹 프레임워크를 사용할 수 있습니다.

개요

App Engine Blobstore에서 Cloud Storage로 마이그레이션하는 프로세스는 개략적으로 다음 단계로 구성됩니다.

  1. 구성 파일 업데이트
  2. Python 앱 업데이트:
    • 웹 프레임워크 업데이트
    • Cloud Storage 가져오기 및 초기화
    • Blobstore 핸들러 업데이트
    • 선택사항: Cloud NDB 또는 App Engine NDB를 사용하는 경우 데이터 모델 업데이트
  3. 앱 테스트 및 배포

구성 파일 업데이트

애플리케이션 코드가 Blobstore에서 Cloud Storage로 이동하도록 수정하기 전에 Cloud Storage 라이브러리를 사용하도록 구성 파일을 업데이트합니다.

  1. app.yaml 파일을 업데이트합니다. 사용 중인 Python 버전에 해당하는 안내를 따르세요.

    Python 2

    Python 2 앱의 경우:

    1. handlers 섹션과 libraries 섹션의 불필요한 웹앱 종속 항목을 삭제합니다.
    2. Cloud 클라이언트 라이브러리를 사용하는 경우 최신 버전의 grpciosetuptools 라이브러리를 추가합니다.
    3. Cloud Storage에 필요하므로 ssl 라이브러리를 추가합니다.

    다음은 변경사항이 적용된 app.yaml 파일의 예시입니다.

    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 3

    Python 3 앱의 경우 runtime 요소를 제외한 모든 줄을 삭제합니다. 예를 들면 다음과 같습니다.

    runtime: python310 # or another support version
    

    Python 3 런타임은 라이브러리를 자동으로 설치하므로 이전 Python 2 런타임의 기본 제공 라이브러리를 지정할 필요가 없습니다. Cloud Storage로 마이그레이션할 때 Python 3 앱에서 다른 기존 번들 서비스를 사용하는 경우 app.yaml 파일을 그대로 둡니다.

  2. requirements.txt 파일을 업데이트합니다. 사용 중인 Python 버전에 해당하는 안내를 따르세요.

    Python 2

    Cloud Storage용 Cloud 클라이언트 라이브러리를 requirements.txt 파일의 종속 항목 목록에 추가합니다.

    google-cloud-storage
    

    그런 다음 pip install -t lib -r requirements.txt를 실행하여 앱에 사용 가능한 라이브러리 목록을 업데이트합니다.

    Python 3

    Cloud Storage용 Cloud 클라이언트 라이브러리를 requirements.txt 파일의 종속 항목 목록에 추가합니다.

    google-cloud-storage
    

    App Engine은 Python 3 런타임에서 앱 배포 중에 이러한 종속 항목을 자동으로 설치하므로, lib 폴더가 있는 경우 이를 삭제하세요.

  3. Python 2 앱의 경우 앱에서 기본 제공 또는 복사된 라이브러리를 사용한다면 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 앱 업데이트

구성 파일을 수정한 후 Python 앱을 업데이트합니다.

Python 2 웹 프레임워크 업데이트

webapp2 프레임워크를 사용하는 Python 2 앱의 경우 오래된 webapp2 프레임워크에서 마이그레이션하는 것이 좋습니다. Python 2 지원 종료 날짜는 런타임 지원 일정을 참조하세요.

Flask, Django 또는 WSGI와 같은 다른 웹 프레임워크로 마이그레이션할 수 있습니다. Cloud Storage는 webapp2에 대한 종속 항목을 제외하고 Blobstore 핸들러는 지원되지 않으므로 다른 웹앱 관련 라이브러리를 삭제하거나 바꿀 수 있습니다.

webapp2를 계속 사용하기로 선택한 경우 이 가이드의 예시에서 Flask와 함께 Cloud Storage를 사용합니다.

Cloud Storage 외에도 Google Cloud 서비스를 사용하거나 최신 런타임 버전에 액세스하려면 앱을 Python 3 런타임으로 업그레이드하는 것이 좋습니다. 자세한 내용은 Python 2에서 Python 3로 마이그레이션 개요를 참조하세요.

Cloud Storage 가져오기 및 초기화

가져오기 및 초기화 줄을 업데이트하여 애플리케이션 파일을 수정합니다.

  1. 다음과 같은 Blobstore 가져오기 문을 삭제합니다.

    import webapp2
    from google.appengine.ext import blobstore
    from google.appengine.ext.webapp import blobstore_handlers
    
  2. 다음과 같이 Cloud Storage 및 Google 인증 라이브러리에 대한 가져오기 문을 추가합니다.

    import io
    from flask import (Flask, abort, redirect, render_template,
    request, send_file, url_for)
    from google.cloud import storage
    import google.auth
    

    Cloud Storage의 Blobstore에 사용된 프로젝트 ID와 같은 프로젝트 ID를 가져오려면 Google 인증 라이브러리가 필요합니다. 앱에 해당하는 경우 Cloud NBD와 같은 다른 라이브러리를 가져옵니다.

  3. Cloud Storage용 새 클라이언트를 만들고 Blobstore에서 사용되는 버킷을 지정합니다. 예를 들면 다음과 같습니다.

    gcs_client = storage.Client()
    _, PROJECT_ID = google.auth.default()
    BUCKET = '%s.appspot.com' % PROJECT_ID
    

    2016년 11월 이후의 Google Cloud 프로젝트에서 Blobstore는 앱의 URL에 따라 이름이 지정된 Cloud Storage 버킷에 쓰고 PROJECT_ID.appspot.com 형식을 따릅니다. Google 인증을 사용하여 프로젝트 ID를 가져와 Blobstore에 blob을 저장하는 데 사용되는 Cloud Storage 버킷을 지정합니다.

Blobstore 핸들러 업데이트

Cloud Storage는 Blobstore 업로드 및 다운로드 핸들러를 지원하지 않으므로 Cloud Storage 기능, io 표준 라이브러리 모듈, 웹 프레임워크, Python 유틸리티를 조합하여 Cloud Storage의 객체(blob)를 업로드 및 다운로드해야 합니다.

다음은 Flask를 웹 프레임워크 예시로 사용하여 Blobstore 핸들러를 업데이트하는 방법을 보여줍니다.

  1. Blobstore 업로드 핸들러 클래스를 Flask의 업로드 함수로 바꿉니다. 사용 중인 Python 버전에 해당하는 안내를 따르세요.

    Python 2

    Python 2의 Blobstore 핸들러는 다음 Blobstore 예시와 같은 webapp2 클래스입니다.

    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 = webapp2.WSGIApplication([
        ('/', MainHandler),
        ('/upload', UploadHandler),
        ('/view/([^/]+)?', ViewBlobHandler),
    ], debug=True)
    

    Cloud Storage를 사용하려면 다음 안내를 따르세요.

    1. 웹앱 업로드 클래스를 Flask 업로드 함수로 바꿉니다.
    2. 업로드 핸들러와 라우팅을 라우팅으로 데코레이션된 Flask POST 메서드로 바꿉니다.

    업데이트된 코드 샘플:

    @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)
    

    업데이트된 Cloud Storage 코드 샘플에서 앱은 이제 blob_id 대신 객체 이름(fname)으로 객체 아티팩트를 식별합니다. 라우팅은 애플리케이션 파일 하단에서도 발생합니다.

    업로드된 객체를 가져오기 위해 Blobstore의 get_uploads() 메서드가 Flask의 request.files.get() 메서드로 대체됩니다. Flask에서는 secure_filename() 메서드를 사용하여 파일에 /와 같은 경로 문자가 없는 이름을 가져오고, gcs_client.bucket(BUCKET).blob(fname)을 사용하여 버킷 이름과 객체 이름을 지정하여 객체를 식별합니다.

    업데이트된 예시와 같이 Cloud Storage upload_from_file() 호출에서 업로드를 수행합니다.

    Python 3

    Python 3용 Blobstore의 업로드 핸들러 클래스는 유틸리티 클래스이며 다음 Blobstore 예시와 같이 WSGI environ 사전을 입력 매개변수로 사용해야 합니다.

    class UploadHandler(blobstore.BlobstoreUploadHandler):
        'Upload blob (POST) handler'
        def post(self):
            uploads = self.get_uploads(request.environ)
            if uploads:
                blob_id = uploads[0].key()
                store_visit(request.remote_addr, request.user_agent, blob_id)
            return redirect('/', code=307)
    ...
    @app.route('/upload', methods=['POST'])
    def upload():
        """Upload handler called by blobstore when a blob is uploaded in the test."""
        return UploadHandler().post()
    

    Cloud Storage를 사용하려면 Blobstore의 get_uploads(request.environ) 메서드를 Flask의 request.files.get() 메서드로 바꿉니다.

    업데이트된 코드 샘플:

    @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)
    

    업데이트된 Cloud Storage 코드 샘플에서 앱은 이제 blob_id 대신 객체 이름(fname)으로 객체 아티팩트를 식별합니다. 라우팅은 애플리케이션 파일 하단에서도 발생합니다.

    업로드된 객체를 가져오기 위해 Blobstore의 get_uploads() 메서드가 Flask의 request.files.get() 메서드로 대체됩니다. Flask에서는 secure_filename() 메서드를 사용하여 파일에 /와 같은 경로 문자가 없는 이름을 가져오고, gcs_client.bucket(BUCKET).blob(fname)을 사용하여 버킷 이름과 객체 이름을 지정하여 객체를 식별합니다.

    Cloud Storage upload_from_file() 메서드는 업데이트된 예시와 같이 업로드를 수행합니다.

  2. Blobstore 다운로드 핸들러 클래스를 Flask의 다운로드 함수로 바꿉니다. 사용 중인 Python 버전에 해당하는 안내를 따르세요.

    Python 2

    다음 다운로드 핸들러 예시에서는 webapp2를 사용하는 BlobstoreDownloadHandler 클래스를 사용하는 방법을 보여줍니다.

    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 = webapp2.WSGIApplication([
        ('/', MainHandler),
        ('/upload', UploadHandler),
        ('/view/([^/]+)?', ViewBlobHandler),
    ], debug=True)
    

    Cloud Storage를 사용하려면 다음 안내를 따르세요.

    1. Cloud Storage의 download_as_bytes() 메서드를 사용하도록 Blobstore의 send_blob() 메서드를 업데이트합니다.
    2. 라우팅을 webapp2에서 Flask로 변경합니다.

    업데이트된 코드 샘플:

    @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)
    

    업데이트된 Cloud Storage 코드 샘플에서 Flask는 Flask 함수의 경로를 데코레이션하고 '/view/<path:fname>'를 사용하여 객체를 식별합니다. Cloud Storage는 객체 이름과 버킷 이름으로 blob 객체를 식별하고 Blobstore의 send_blob 메서드를 사용하는 대신 download_as_bytes() 메서드를 사용하여 객체를 바이트로 다운로드합니다. 아티팩트가 발견되지 않으면 앱에서 HTTP 404 오류를 반환합니다.

    Python 3

    Python 3용 Blobstore의 다운로드 핸들러 클래스는 업로드 핸들러와 마찬가지로 유틸리티 클래스이며 다음 Blobstore 예시와 같이 WSGI environ 사전을 입력 매개변수로 사용해야 합니다.

    class ViewBlobHandler(blobstore.BlobstoreDownloadHandler):
        'view uploaded blob (GET) handler'
        def get(self, blob_key):
            if not blobstore.get(blob_key):
                return "Photo key not found", 404
            else:
                headers = self.send_blob(request.environ, blob_key)
    
            # Prevent Flask from setting a default content-type.
            # GAE sets it to a guessed type if the header is not set.
            headers['Content-Type'] = None
            return '', headers
    ...
    @app.route('/view/<blob_key>')
    def view_photo(blob_key):
        """View photo given a key."""
        return ViewBlobHandler().get(blob_key)
    

    Cloud Storage를 사용하려면 Blobstore의 send_blob(request.environ, blob_key)을 Cloud Storage의 blob.download_as_bytes() 메서드로 바꿉니다.

    업데이트된 코드 샘플:

    @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)
    

    업데이트된 Cloud Storage 코드 샘플에서 blob_keyfname으로 바뀌고 Flask는 '/view/<path:fname>' URL을 사용하여 객체를 식별합니다. gcs_client.bucket(BUCKET).blob(fname) 메서드는 파일 이름과 버킷 이름을 찾는 데 사용됩니다. Cloud Storage의 download_as_bytes() 메서드는 Blobstore의 send_blob() 메서드를 사용하는 대신 객체를 바이트로 다운로드합니다.

  3. 앱에서 기본 핸들러를 사용하는 경우 Flask에서 MainHandler 클래스를 root() 함수로 바꿉니다. 사용 중인 Python 버전에 해당하는 안내를 따르세요.

    Python 2

    다음은 Blobstore의 MainHandler 클래스를 사용하는 예시입니다.

    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)
    

    Cloud Storage를 사용하려면 다음 안내를 따르세요.

    1. Flask에서 자동으로 라우팅을 처리하므로 MainHandler(BaseHandler) 클래스를 삭제합니다.
    2. Flask로 Blobstore 코드를 단순화합니다.
    3. 끝에 있는 웹앱 라우팅을 삭제합니다.

    업데이트된 코드 샘플:

    @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)
    

    Python 3

    Flask를 사용한 경우 MainHandler 클래스는 없지만 blobstore를 사용하면 Flask 루트 함수를 업데이트해야 합니다. 다음 예시에서는 blobstore.create_upload_url('/upload') 함수를 사용합니다.

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

    Cloud Storage를 사용하려면 blobstore.create_upload_url('/upload') 함수를 Flask의 url_for() 메서드로 바꿔 upload() 함수의 URL을 가져옵니다.

    업데이트된 코드 샘플:

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

앱 테스트 및 배포

로컬 개발 서버를 사용하면 앱이 실행되는지 테스트할 수 있지만 모든 Cloud Storage 요청을 인터넷을 통해 실제 Cloud Storage 버킷에 보내야 하므로 새 버전을 배포할 때까지 Cloud Storage를 테스트할 수 없습니다. 애플리케이션을 로컬에서 실행하는 방법은 애플리케이션 테스트 및 배포를 참조하세요. 그런 다음 새 버전을 배포하여 앱이 이전과 동일하게 표시되는지 확인합니다.

App Engine NDB 또는 Cloud NDB를 사용하는 앱

앱에서 App Engine NDB 또는 Cloud NDB를 사용하여 Blobstore 관련 속성을 포함하는 경우 Datastore 데이터 모델을 업데이트해야 합니다.

데이터 모델 업데이트

NDB의 BlobKey 속성은 Cloud Storage에서 지원되지 않으므로 NDB, 웹 프레임워크 또는 다른 곳의 기본 제공 등가 항목을 사용하도록 Blobstore 관련 줄을 수정해야 합니다.

데이터 모델을 업데이트하려면 다음 안내를 따르세요.

  1. 다음과 같이 데이터 모델에서 BlobKey를 사용하는 줄을 찾습니다.

    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()
    
  2. ndb.BlobKeyProperty()ndb.StringProperty()로 바꿉니다.

    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() # Modified from ndb.BlobKeyProperty()
    
  3. 마이그레이션 중에 App Engine NDB에서 Cloud NDB로 업그레이드하는 경우 Python 컨텍스트 관리자를 사용하도록 NDB 코드를 리팩터링하는 방법에 대한 안내는 Cloud NDB 마이그레이션 가이드를 참조하세요.

Datastore 데이터 모델의 하위 호환성

이전 섹션에서 ndb.BlobKeyPropertyndb.StringProperty로 바꾸면 앱이 이전 버전과 호환되지 않게 됩니다. 즉, 앱에서 Blobstore에서 만든 이전 항목을 처리할 수 없습니다. 이전 데이터를 유지해야 하는 경우 ndb.BlobKeyProperty 필드를 업데이트하는 대신 새 Cloud Storage 항목의 필드를 추가로 만들고 데이터를 정규화하는 함수를 만듭니다.

이전 섹션의 예시에서 다음과 같이 변경합니다.

  1. 데이터 모델을 정의할 때 별도 속성 필드 2개를 만듭니다. file_blob 속성을 사용하여 Blobstore에서 만든 객체를 식별하고 file_gcs 속성을 사용하여 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()
    
  2. 다음과 같이 신규 방문을 참조하는 줄을 찾습니다.

    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()
    
  3. file_gcs가 최근 항목에 사용되도록 코드를 변경합니다. 예를 들면 다음과 같습니다.

    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() # change file_blob to file_gcs for new requests
    
  4. 데이터를 정규화하는 새 함수를 만듭니다. 다음 예시에서는 추출, 변환, 로드(ETL)를 사용하여 모든 방문을 반복하고, 방문자 및 타임스탬프 데이터를 가져와 file_gcs 또는 file_gcs가 있는지 확인합니다.

    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]
    
  5. fetch_visits() 함수를 참조하는 줄을 찾습니다.

    @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)
    
  6. etl_visits() 함수 내에서 fetch_visits()를 래핑합니다. 예를 들면 다음과 같습니다.

    @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)) # etl_visits wraps around fetch_visits
        return render_template('index.html', **context)
    

예시

다음 단계