├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app-engine ├── app.yaml ├── appengine_config.py ├── bookshelf │ ├── __init__.py │ ├── crud.py │ ├── model_cloudsql.py │ ├── model_datastore.py │ └── templates │ │ ├── base.html │ │ ├── form.html │ │ ├── list.html │ │ └── view.html ├── config.py ├── main.py └── requirements.txt ├── cloud-storage ├── app.yaml ├── appengine_config.py ├── bookshelf │ ├── __init__.py │ ├── crud.py │ ├── model_cloudsql.py │ ├── model_datastore.py │ ├── storage.py │ └── templates │ │ ├── base.html │ │ ├── form.html │ │ ├── list.html │ │ └── view.html ├── config.py ├── main.py ├── requirements.txt └── tempfile2.py ├── compute-engine ├── bookshelf │ ├── __init__.py │ ├── crud.py │ ├── model_cloudsql.py │ ├── model_datastore.py │ ├── storage.py │ └── templates │ │ ├── base.html │ │ ├── form.html │ │ ├── list.html │ │ └── view.html ├── config.py ├── main.py ├── procfile ├── requirements.txt └── startup-scripts │ └── startup-script.sh └── container-engine ├── .dockerignore ├── Dockerfile ├── bookshelf-frontend.yaml ├── bookshelf ├── __init__.py ├── crud.py ├── model_cloudsql.py ├── model_datastore.py ├── storage.py └── templates │ ├── base.html │ ├── form.html │ ├── list.html │ └── view.html ├── config.py ├── main.py ├── procfile └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Distribution / packaging 7 | .Python 8 | env/ 9 | 10 | # Installer logs 11 | pip-log.txt 12 | pip-delete-this-directory.txt 13 | 14 | # Unit test / coverage reports 15 | htmlcov/ 16 | .tox/ 17 | .nox/ 18 | .coverage 19 | .coverage.* 20 | .cache 21 | nosetests.xml 22 | coverage.xml 23 | *,cover 24 | 25 | *.log 26 | 27 | lib/ 28 | *.internal 29 | /secrets.tar.gz 30 | /secrets.tar 31 | /client-secret.json 32 | /service-account.json 33 | /config.py 34 | 35 | .DS_Store 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your sample apps and patches! Before we can take them, we 6 | have to jump a couple of legal hurdles. 7 | 8 | Please fill out either the individual or corporate Contributor License Agreement 9 | (CLA). 10 | 11 | * If you are an individual writing original source code and you're sure you 12 | own the intellectual property, then you'll need to sign an [individual CLA] 13 | (https://developers.google.com/open-source/cla/individual). 14 | * If you work for a company that wants to allow you to contribute your work, 15 | then you'll need to sign a [corporate CLA] 16 | (https://developers.google.com/open-source/cla/corporate). 17 | 18 | Follow either of the two links above to access the appropriate CLA and 19 | instructions for how to sign and return it. Once we receive it, we'll be able to 20 | accept your pull requests. 21 | 22 | ## Contributing A Patch 23 | 24 | 1. Submit an issue describing your proposed change to the repo in question. 25 | 1. The repo owner will respond to your issue promptly. 26 | 1. If your proposed change is accepted, and you haven't already done so, sign a 27 | Contributor License Agreement (see details above). 28 | 1. Fork the desired repo, develop and test your code changes. 29 | 1. Ensure that your code adheres to the existing style in the sample to which 30 | you are contributing. Refer to the 31 | [Google Cloud Platform Samples Style Guide] 32 | (https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the 33 | recommended coding standards for this organization. 34 | 1. Ensure that your code has an appropriate set of unit tests which all pass. 35 | 1. Submit a pull request. 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Google Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a fork from [GCPTraining bookshelf app](https://github.com/GoogleCloudPlatformTraining/cp100-bookshelf) 2 | 3 | The following changes were made to fix two errors when deploying the Bookshelf application. 4 | 5 | 1. ImportError: cannot import name SpooledTemporaryFile 6 | fix: added line 8&9 to [cp100-bookshelf/app-engine/appengine_config.py](https://github.com/Flora7/cp100-bookshelf/blob/master/app-engine/appengine_config.py) 7 | 2. TemporaryFile() got an unexpected keyword argument 'max_size' 8 | fix: added file [cp100-bookshelf/cloud-storage/tempfile2.py](https://github.com/Flora7/cp100-bookshelf/blob/master/cloud-storage/tempfile2.py) and added line 8-10 to [cp100-bookshelf/cloud-storage/appengine_config.py](https://github.com/Flora7/cp100-bookshelf/blob/master/cloud-storage/appengine_config.py). 9 | 10 | # cp100A-bookshelf-python 11 | Used in the CP100A course - Collected sample code for the Bookshelf application. 12 | 13 | ## Contributing changes 14 | 15 | * See [CONTRIBUTING.md](CONTRIBUTING.md) 16 | 17 | 18 | ## Licensing 19 | 20 | * See [LICENSE](LICENSE) 21 | -------------------------------------------------------------------------------- /app-engine/app.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # This file specifies your Python application's runtime configuration. 16 | # See https://cloud.google.com/appengine/docs/python/config/appconfig 17 | # for details. 18 | 19 | runtime: python27 20 | api_version: 1 21 | threadsafe: true 22 | 23 | handlers: 24 | - url: /.* 25 | script: main.app -------------------------------------------------------------------------------- /app-engine/appengine_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | `appengine_config.py` is automatically loaded when Google App Engine 3 | starts a new instance of your application. This runs before any 4 | WSGI applications specified in app.yaml are loaded. 5 | """ 6 | 7 | from google.appengine.ext import vendor 8 | import tempfile 9 | tempfile.SpooledTemporaryFile = tempfile.TemporaryFile 10 | 11 | # Third-party libraries are stored in "lib", vendoring will make 12 | # sure that they are importable by the application. 13 | vendor.add('lib') 14 | -------------------------------------------------------------------------------- /app-engine/bookshelf/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | 17 | from flask import current_app, Flask, redirect, url_for 18 | 19 | 20 | def create_app(config, debug=False, testing=False, config_overrides=None): 21 | app = Flask(__name__) 22 | app.config.from_object(config) 23 | 24 | app.debug = debug 25 | app.testing = testing 26 | 27 | if config_overrides: 28 | app.config.update(config_overrides) 29 | 30 | # Configure logging 31 | if not app.testing: 32 | logging.basicConfig(level=logging.INFO) 33 | 34 | # Setup the data model. 35 | with app.app_context(): 36 | model = get_model() 37 | model.init_app(app) 38 | 39 | # Register the Bookshelf CRUD blueprint. 40 | from .crud import crud 41 | app.register_blueprint(crud, url_prefix='/books') 42 | 43 | # Add a default root route. 44 | @app.route("/") 45 | def index(): 46 | return redirect(url_for('crud.list')) 47 | 48 | # Add an error handler. This is useful for debugging the live application, 49 | # however, you should disable the output of the exception for production 50 | # applications. 51 | @app.errorhandler(500) 52 | def server_error(e): 53 | return """ 54 | An internal error occurred:
{}
55 | See logs for full stacktrace. 56 | """.format(e), 500 57 | 58 | return app 59 | 60 | 61 | def get_model(): 62 | model_backend = current_app.config['DATA_BACKEND'] 63 | if model_backend == 'cloudsql': 64 | from . import model_cloudsql 65 | model = model_cloudsql 66 | elif model_backend == 'datastore': 67 | from . import model_datastore 68 | model = model_datastore 69 | else: 70 | raise ValueError( 71 | "No appropriate databackend configured. " 72 | "Please specify datastore, or cloudsql") 73 | 74 | return model 75 | -------------------------------------------------------------------------------- /app-engine/bookshelf/crud.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from bookshelf import get_model 16 | from flask import Blueprint, redirect, render_template, request, url_for 17 | 18 | 19 | crud = Blueprint('crud', __name__) 20 | 21 | 22 | # [START list] 23 | @crud.route("/") 24 | def list(): 25 | token = request.args.get('page_token', None) 26 | books, next_page_token = get_model().list(cursor=token) 27 | 28 | return render_template( 29 | "list.html", 30 | books=books, 31 | next_page_token=next_page_token) 32 | # [END list] 33 | 34 | 35 | @crud.route('/') 36 | def view(id): 37 | book = get_model().read(id) 38 | return render_template("view.html", book=book) 39 | 40 | 41 | # [START add] 42 | @crud.route('/add', methods=['GET', 'POST']) 43 | def add(): 44 | if request.method == 'POST': 45 | data = request.form.to_dict(flat=True) 46 | 47 | book = get_model().create(data) 48 | 49 | return redirect(url_for('.view', id=book['id'])) 50 | 51 | return render_template("form.html", action="Add", book={}) 52 | # [END add] 53 | 54 | 55 | @crud.route('//edit', methods=['GET', 'POST']) 56 | def edit(id): 57 | book = get_model().read(id) 58 | 59 | if request.method == 'POST': 60 | data = request.form.to_dict(flat=True) 61 | 62 | book = get_model().update(data, id) 63 | 64 | return redirect(url_for('.view', id=book['id'])) 65 | 66 | return render_template("form.html", action="Edit", book=book) 67 | 68 | 69 | @crud.route('//delete') 70 | def delete(id): 71 | get_model().delete(id) 72 | return redirect(url_for('.list')) 73 | -------------------------------------------------------------------------------- /app-engine/bookshelf/model_cloudsql.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from flask import Flask 16 | from flask.ext.sqlalchemy import SQLAlchemy 17 | 18 | 19 | builtin_list = list 20 | 21 | 22 | db = SQLAlchemy() 23 | 24 | 25 | def init_app(app): 26 | db.init_app(app) 27 | 28 | 29 | def from_sql(row): 30 | """Translates a SQLAlchemy model instance into a dictionary""" 31 | data = row.__dict__.copy() 32 | data['id'] = row.id 33 | data.pop('_sa_instance_state') 34 | return data 35 | 36 | 37 | # [START model] 38 | class Book(db.Model): 39 | __tablename__ = 'books' 40 | 41 | id = db.Column(db.Integer, primary_key=True) 42 | title = db.Column(db.String(255)) 43 | author = db.Column(db.String(255)) 44 | publishedDate = db.Column(db.String(255)) 45 | imageUrl = db.Column(db.String(255)) 46 | description = db.Column(db.String(255)) 47 | createdBy = db.Column(db.String(255)) 48 | createdById = db.Column(db.String(255)) 49 | 50 | def __repr__(self): 51 | return " 17 | 18 | 19 | Bookshelf - Python on Google Cloud Platform 20 | 21 | 22 | 23 | 24 | 25 | 35 |
36 | {% block content %}{% endblock %} 37 |
38 | {{user}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /app-engine/bookshelf/templates/form.html: -------------------------------------------------------------------------------- 1 | {# 2 | # Copyright 2016 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | #} 16 | 17 | {# [START form] #} 18 | {% extends "base.html" %} 19 | 20 | {% block content %} 21 |

{{action}} book

22 | 23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 | 38 |
39 | 40 |
41 | 42 | 43 |
44 | 45 | 46 |
47 | 48 | {% endblock %} 49 | {# [END form] #} 50 | -------------------------------------------------------------------------------- /app-engine/bookshelf/templates/list.html: -------------------------------------------------------------------------------- 1 | {# 2 | # Copyright 2016 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | #} 16 | 17 | {% extends "base.html" %} 18 | 19 | {% block content %} 20 | 21 |

Books

22 | 23 | 24 | Add book 25 | 26 | 27 | {% for book in books %} 28 | 39 | {% else %} 40 |

No books found

41 | {% endfor %} 42 | 43 | {% if next_page_token %} 44 | 49 | {% endif %} 50 | 51 | {% endblock %} 52 | -------------------------------------------------------------------------------- /app-engine/bookshelf/templates/view.html: -------------------------------------------------------------------------------- 1 | {# 2 | # Copyright 2016 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | #} 16 | 17 | {% extends "base.html" %} 18 | 19 | {% block content %} 20 | 21 |

Book

22 | 23 | 33 | 34 |
35 |
36 | 37 |
38 |
39 |

40 | {{book.title}} 41 | {{book.publishedDate}} 42 |

43 |
By {{book.author|default('Unknown', True)}}
44 |

{{book.description}}

45 |
46 |
47 | 48 | {% endblock %} 49 | -------------------------------------------------------------------------------- /app-engine/config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This file contains all of the configuration values for the application. 17 | Update this file with the values for your specific Google Cloud project. 18 | You can create and manage projects at https://console.developers.google.com 19 | """ 20 | 21 | # There are two different ways to store the data in the application. 22 | # You can choose 'datastore', or 'cloudsql'. Be sure to 23 | # configure the respective settings for the one you choose below. 24 | # You do not have to configure the other data backend. If unsure, choose 25 | # 'datastore' as it does not require any additional configuration. 26 | DATA_BACKEND = 'datastore' 27 | 28 | # SQLAlchemy configuration 29 | # Replace user, pass, host, and database with the respective values of your 30 | # Cloud SQL instance. 31 | SQLALCHEMY_DATABASE_URI = \ 32 | 'mysql+pymysql://user:password@host/database' 33 | -------------------------------------------------------------------------------- /app-engine/main.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import bookshelf 16 | import config 17 | 18 | 19 | app = bookshelf.create_app(config) 20 | 21 | 22 | # This is only used when running locally. When running live, gunicorn runs 23 | # the application. 24 | if __name__ == '__main__': 25 | app.run(host='127.0.0.1', port=8080, debug=True) 26 | -------------------------------------------------------------------------------- /app-engine/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.11.1 2 | gunicorn==19.6.0 3 | -------------------------------------------------------------------------------- /cloud-storage/app.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # This file specifies your Python application's runtime configuration. 16 | # See https://cloud.google.com/appengine/docs/python/config/appconfig 17 | # for details. 18 | 19 | runtime: python27 20 | api_version: 1 21 | threadsafe: true 22 | 23 | handlers: 24 | - url: /.* 25 | script: main.app -------------------------------------------------------------------------------- /cloud-storage/appengine_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | `appengine_config.py` is automatically loaded when Google App Engine 3 | starts a new instance of your application. This runs before any 4 | WSGI applications specified in app.yaml are loaded. 5 | """ 6 | 7 | from google.appengine.ext import vendor 8 | import tempfile 9 | import tempfile2 10 | tempfile.SpooledTemporaryFile = tempfile2.SpooledTemporaryFile 11 | 12 | # Third-party libraries are stored in "lib", vendoring will make 13 | # sure that they are importable by the application. 14 | vendor.add('lib') 15 | -------------------------------------------------------------------------------- /cloud-storage/bookshelf/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | 17 | from flask import current_app, Flask, redirect, url_for 18 | 19 | 20 | def create_app(config, debug=False, testing=False, config_overrides=None): 21 | app = Flask(__name__) 22 | app.config.from_object(config) 23 | 24 | app.debug = debug 25 | app.testing = testing 26 | 27 | if config_overrides: 28 | app.config.update(config_overrides) 29 | 30 | # Configure logging 31 | if not app.testing: 32 | logging.basicConfig(level=logging.INFO) 33 | 34 | # Setup the data model. 35 | with app.app_context(): 36 | model = get_model() 37 | model.init_app(app) 38 | 39 | # Register the Bookshelf CRUD blueprint. 40 | from .crud import crud 41 | app.register_blueprint(crud, url_prefix='/books') 42 | 43 | # Add a default root route. 44 | @app.route("/") 45 | def index(): 46 | return redirect(url_for('crud.list')) 47 | 48 | # Add an error handler. This is useful for debugging the live application, 49 | # however, you should disable the output of the exception for production 50 | # applications. 51 | @app.errorhandler(500) 52 | def server_error(e): 53 | return """ 54 | An internal error occurred:
{}
55 | See logs for full stacktrace. 56 | """.format(e), 500 57 | 58 | return app 59 | 60 | 61 | def get_model(): 62 | model_backend = current_app.config['DATA_BACKEND'] 63 | if model_backend == 'cloudsql': 64 | from . import model_cloudsql 65 | model = model_cloudsql 66 | elif model_backend == 'datastore': 67 | from . import model_datastore 68 | model = model_datastore 69 | else: 70 | raise ValueError( 71 | "No appropriate databackend configured. " 72 | "Please specify datastore, or cloudsql") 73 | 74 | return model 75 | 76 | -------------------------------------------------------------------------------- /cloud-storage/bookshelf/crud.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from bookshelf import get_model, storage 16 | from flask import Blueprint, current_app, redirect, render_template, request, \ 17 | url_for 18 | 19 | 20 | crud = Blueprint('crud', __name__) 21 | 22 | 23 | # [START upload_image_file] 24 | def upload_image_file(file): 25 | """ 26 | Upload the user-uploaded file to Google Cloud Storage and retrieve its 27 | publicly-accessible URL. 28 | """ 29 | if not file: 30 | return None 31 | 32 | public_url = storage.upload_file( 33 | file.read(), 34 | file.filename, 35 | file.content_type 36 | ) 37 | 38 | current_app.logger.info( 39 | "Uploaded file %s as %s.", file.filename, public_url) 40 | 41 | return public_url 42 | # [END upload_image_file] 43 | 44 | 45 | @crud.route("/") 46 | def list(): 47 | token = request.args.get('page_token', None) 48 | books, next_page_token = get_model().list(cursor=token) 49 | 50 | return render_template( 51 | "list.html", 52 | books=books, 53 | next_page_token=next_page_token) 54 | 55 | 56 | @crud.route('/') 57 | def view(id): 58 | book = get_model().read(id) 59 | return render_template("view.html", book=book) 60 | 61 | 62 | @crud.route('/add', methods=['GET', 'POST']) 63 | def add(): 64 | if request.method == 'POST': 65 | data = request.form.to_dict(flat=True) 66 | 67 | # If an image was uploaded, update the data to point to the new image. 68 | # [START image_url] 69 | image_url = upload_image_file(request.files.get('image')) 70 | # [END image_url] 71 | 72 | # [START image_url2] 73 | if image_url: 74 | data['imageUrl'] = image_url 75 | # [END image_url2] 76 | 77 | book = get_model().create(data) 78 | 79 | return redirect(url_for('.view', id=book['id'])) 80 | 81 | return render_template("form.html", action="Add", book={}) 82 | 83 | 84 | @crud.route('//edit', methods=['GET', 'POST']) 85 | def edit(id): 86 | book = get_model().read(id) 87 | 88 | if request.method == 'POST': 89 | data = request.form.to_dict(flat=True) 90 | 91 | image_url = upload_image_file(request.files.get('image')) 92 | 93 | if image_url: 94 | data['imageUrl'] = image_url 95 | 96 | book = get_model().update(data, id) 97 | 98 | return redirect(url_for('.view', id=book['id'])) 99 | 100 | return render_template("form.html", action="Edit", book=book) 101 | 102 | 103 | @crud.route('//delete') 104 | def delete(id): 105 | get_model().delete(id) 106 | return redirect(url_for('.list')) 107 | -------------------------------------------------------------------------------- /cloud-storage/bookshelf/model_cloudsql.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from flask import Flask 16 | from flask.ext.sqlalchemy import SQLAlchemy 17 | 18 | 19 | builtin_list = list 20 | 21 | 22 | db = SQLAlchemy() 23 | 24 | 25 | def init_app(app): 26 | db.init_app(app) 27 | 28 | 29 | def from_sql(row): 30 | """Translates a SQLAlchemy model instance into a dictionary""" 31 | data = row.__dict__.copy() 32 | data['id'] = row.id 33 | data.pop('_sa_instance_state') 34 | return data 35 | 36 | 37 | # [START model] 38 | class Book(db.Model): 39 | __tablename__ = 'books' 40 | 41 | id = db.Column(db.Integer, primary_key=True) 42 | title = db.Column(db.String(255)) 43 | author = db.Column(db.String(255)) 44 | publishedDate = db.Column(db.String(255)) 45 | imageUrl = db.Column(db.String(255)) 46 | description = db.Column(db.String(255)) 47 | createdBy = db.Column(db.String(255)) 48 | createdById = db.Column(db.String(255)) 49 | 50 | def __repr__(self): 51 | return " 17 | 18 | 19 | Bookshelf - Python on Google Cloud Platform 20 | 21 | 22 | 23 | 24 | 25 | 35 |
36 | {% block content %}{% endblock %} 37 |
38 | {{user}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /cloud-storage/bookshelf/templates/form.html: -------------------------------------------------------------------------------- 1 | {# 2 | # Copyright 2016 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | #} 16 | 17 | {# [START form] #} 18 | {% extends "base.html" %} 19 | 20 | {% block content %} 21 |

{{action}} book

22 | 23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 | 38 |
39 | 40 |
41 | 42 | 43 |
44 | 45 |
46 | 47 | 48 |
49 | 50 | 54 | 55 | 56 |
57 | 58 | {% endblock %} 59 | {# [END form] #} 60 | -------------------------------------------------------------------------------- /cloud-storage/bookshelf/templates/list.html: -------------------------------------------------------------------------------- 1 | {# 2 | # Copyright 2016 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | #} 16 | 17 | {% extends "base.html" %} 18 | 19 | {% block content %} 20 | 21 |

Books

22 | 23 | 24 | Add book 25 | 26 | 27 | {% for book in books %} 28 | 43 | {% else %} 44 |

No books found

45 | {% endfor %} 46 | 47 | {% if next_page_token %} 48 | 53 | {% endif %} 54 | 55 | {% endblock %} 56 | -------------------------------------------------------------------------------- /cloud-storage/bookshelf/templates/view.html: -------------------------------------------------------------------------------- 1 | {# 2 | # Copyright 2016 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | #} 16 | 17 | {% extends "base.html" %} 18 | 19 | {% block content %} 20 | 21 |

Book

22 | 23 | 33 | 34 |
35 | {# [START book_image] #} 36 |
37 | {% if book.imageUrl %} 38 | 39 | {% else %} 40 | 41 | {% endif %} 42 |
43 | {# [END book_image] #} 44 |
45 |

46 | {{book.title}} 47 | {{book.publishedDate}} 48 |

49 |
By {{book.author|default('Unknown', True)}}
50 |

{{book.description}}

51 |
52 |
53 | 54 | {% endblock %} 55 | -------------------------------------------------------------------------------- /cloud-storage/config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This file contains all of the configuration values for the application. 17 | Update this file with the values for your specific Google Cloud project. 18 | You can create and manage projects at https://console.developers.google.com 19 | """ 20 | 21 | # There are two different ways to store the data in the application. 22 | # You can choose 'datastore', or 'cloudsql'. Be sure to 23 | # configure the respective settings for the one you choose below. 24 | # You do not have to configure the other data backend. If unsure, choose 25 | # 'datastore' as it does not require any additional configuration. 26 | DATA_BACKEND = 'datastore' 27 | 28 | # SQLAlchemy configuration 29 | # Replace user, pass, host, and database with the respective values of your 30 | # Cloud SQL instance. 31 | SQLALCHEMY_DATABASE_URI = \ 32 | 'mysql+pymysql://user:password@host/database' 33 | 34 | # Google Cloud Storage and upload settings. 35 | # Typically, you'll name your bucket the same as your project. To create a 36 | # bucket: 37 | # 38 | # $ gsutil mb gs:// 39 | # 40 | # You also need to make sure that the default ACL is set to public-read, 41 | # otherwise users will not be able to see their upload images: 42 | # 43 | # $ gsutil defacl set public-read gs:// 44 | # 45 | # You can adjust the max content length and allow extensions settings to allow 46 | # larger or more varied file types if desired. 47 | CLOUD_STORAGE_BUCKET = 'your-bucket-name' 48 | MAX_CONTENT_LENGTH = 8 * 1024 * 1024 49 | ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif']) 50 | -------------------------------------------------------------------------------- /cloud-storage/main.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import bookshelf 16 | import config 17 | 18 | 19 | app = bookshelf.create_app(config) 20 | 21 | 22 | # This is only used when running locally. When running live, gunicorn runs 23 | # the application. 24 | if __name__ == '__main__': 25 | app.run(host='127.0.0.1', port=8080, debug=True) 26 | -------------------------------------------------------------------------------- /cloud-storage/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.11.1 2 | GoogleAppEngineCloudStorageClient==1.9.22.1 3 | gunicorn==19.6.0 4 | six==1.10.0 5 | -------------------------------------------------------------------------------- /cloud-storage/tempfile2.py: -------------------------------------------------------------------------------- 1 | """Temporary files. 2 | 3 | This module provides generic, low- and high-level interfaces for 4 | creating temporary files and directories. The interfaces listed 5 | as "safe" just below can be used without fear of race conditions. 6 | Those listed as "unsafe" cannot, and are provided for backward 7 | compatibility only. 8 | 9 | This module also provides some data items to the user: 10 | 11 | TMP_MAX - maximum number of names that will be tried before 12 | giving up. 13 | template - the default prefix for all temporary names. 14 | You may change this to control the default prefix. 15 | tempdir - If this is set to a string before the first use of 16 | any routine from this module, it will be considered as 17 | another candidate location to store temporary files. 18 | """ 19 | 20 | __all__ = [ 21 | "NamedTemporaryFile", "TemporaryFile", # high level safe interfaces 22 | "SpooledTemporaryFile", 23 | "mkstemp", "mkdtemp", # low level safe interfaces 24 | "mktemp", # deprecated unsafe interface 25 | "TMP_MAX", "gettempprefix", # constants 26 | "tempdir", "gettempdir" 27 | ] 28 | 29 | 30 | # Imports. 31 | 32 | import os as _os 33 | import errno as _errno 34 | from random import Random as _Random 35 | 36 | try: 37 | from cStringIO import StringIO as _StringIO 38 | except ImportError: 39 | from StringIO import StringIO as _StringIO 40 | 41 | try: 42 | import fcntl as _fcntl 43 | except ImportError: 44 | def _set_cloexec(fd): 45 | pass 46 | else: 47 | def _set_cloexec(fd): 48 | try: 49 | flags = _fcntl.fcntl(fd, _fcntl.F_GETFD, 0) 50 | except IOError: 51 | pass 52 | else: 53 | # flags read successfully, modify 54 | flags |= _fcntl.FD_CLOEXEC 55 | _fcntl.fcntl(fd, _fcntl.F_SETFD, flags) 56 | 57 | 58 | try: 59 | import thread as _thread 60 | except ImportError: 61 | import dummy_thread as _thread 62 | _allocate_lock = _thread.allocate_lock 63 | 64 | _text_openflags = _os.O_RDWR | _os.O_CREAT | _os.O_EXCL 65 | if hasattr(_os, 'O_NOINHERIT'): 66 | _text_openflags |= _os.O_NOINHERIT 67 | if hasattr(_os, 'O_NOFOLLOW'): 68 | _text_openflags |= _os.O_NOFOLLOW 69 | 70 | _bin_openflags = _text_openflags 71 | if hasattr(_os, 'O_BINARY'): 72 | _bin_openflags |= _os.O_BINARY 73 | 74 | if hasattr(_os, 'TMP_MAX'): 75 | TMP_MAX = _os.TMP_MAX 76 | else: 77 | TMP_MAX = 10000 78 | 79 | template = "tmp" 80 | 81 | # Internal routines. 82 | 83 | _once_lock = _allocate_lock() 84 | 85 | if hasattr(_os, "lstat"): 86 | _stat = _os.lstat 87 | elif hasattr(_os, "stat"): 88 | _stat = _os.stat 89 | else: 90 | # Fallback. All we need is something that raises os.error if the 91 | # file doesn't exist. 92 | def _stat(fn): 93 | try: 94 | f = open(fn) 95 | except IOError: 96 | raise _os.error 97 | f.close() 98 | 99 | def _exists(fn): 100 | try: 101 | _stat(fn) 102 | except _os.error: 103 | return False 104 | else: 105 | return True 106 | 107 | class _RandomNameSequence: 108 | """An instance of _RandomNameSequence generates an endless 109 | sequence of unpredictable strings which can safely be incorporated 110 | into file names. Each string is six characters long. Multiple 111 | threads can safely use the same instance at the same time. 112 | 113 | _RandomNameSequence is an iterator.""" 114 | 115 | characters = ("abcdefghijklmnopqrstuvwxyz" + 116 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + 117 | "0123456789_") 118 | 119 | def __init__(self): 120 | self.mutex = _allocate_lock() 121 | self.rng = _Random() 122 | self.normcase = _os.path.normcase 123 | 124 | def __iter__(self): 125 | return self 126 | 127 | def next(self): 128 | m = self.mutex 129 | c = self.characters 130 | choose = self.rng.choice 131 | 132 | m.acquire() 133 | try: 134 | letters = [choose(c) for dummy in "123456"] 135 | finally: 136 | m.release() 137 | 138 | return self.normcase(''.join(letters)) 139 | 140 | def _candidate_tempdir_list(): 141 | """Generate a list of candidate temporary directories which 142 | _get_default_tempdir will try.""" 143 | 144 | dirlist = [] 145 | 146 | # First, try the environment. 147 | for envname in 'TMPDIR', 'TEMP', 'TMP': 148 | dirname = _os.getenv(envname) 149 | if dirname: dirlist.append(dirname) 150 | 151 | # Failing that, try OS-specific locations. 152 | if _os.name == 'riscos': 153 | dirname = _os.getenv('Wimp$ScrapDir') 154 | if dirname: dirlist.append(dirname) 155 | elif _os.name == 'nt': 156 | dirlist.extend([ r'c:\temp', r'c:\tmp', r'\temp', r'\tmp' ]) 157 | else: 158 | dirlist.extend([ '/tmp', '/var/tmp', '/usr/tmp' ]) 159 | 160 | # As a last resort, the current directory. 161 | try: 162 | dirlist.append(_os.getcwd()) 163 | except (AttributeError, _os.error): 164 | dirlist.append(_os.curdir) 165 | 166 | return dirlist 167 | 168 | def _get_default_tempdir(): 169 | """Calculate the default directory to use for temporary files. 170 | This routine should be called exactly once. 171 | 172 | We determine whether or not a candidate temp dir is usable by 173 | trying to create and write to a file in that directory. If this 174 | is successful, the test file is deleted. To prevent denial of 175 | service, the name of the test file must be randomized.""" 176 | 177 | namer = _RandomNameSequence() 178 | dirlist = _candidate_tempdir_list() 179 | flags = _text_openflags 180 | 181 | for dir in dirlist: 182 | if dir != _os.curdir: 183 | dir = _os.path.normcase(_os.path.abspath(dir)) 184 | # Try only a few names per directory. 185 | for seq in xrange(100): 186 | name = namer.next() 187 | filename = _os.path.join(dir, name) 188 | try: 189 | fd = _os.open(filename, flags, 0600) 190 | fp = _os.fdopen(fd, 'w') 191 | fp.write('blat') 192 | fp.close() 193 | _os.unlink(filename) 194 | del fp, fd 195 | return dir 196 | except (OSError, IOError), e: 197 | if e[0] != _errno.EEXIST: 198 | break # no point trying more names in this directory 199 | pass 200 | raise IOError, (_errno.ENOENT, 201 | ("No usable temporary directory found in %s" % dirlist)) 202 | 203 | _name_sequence = None 204 | 205 | def _get_candidate_names(): 206 | """Common setup sequence for all user-callable interfaces.""" 207 | 208 | global _name_sequence 209 | if _name_sequence is None: 210 | _once_lock.acquire() 211 | try: 212 | if _name_sequence is None: 213 | _name_sequence = _RandomNameSequence() 214 | finally: 215 | _once_lock.release() 216 | return _name_sequence 217 | 218 | 219 | def _mkstemp_inner(dir, pre, suf, flags): 220 | """Code common to mkstemp, TemporaryFile, and NamedTemporaryFile.""" 221 | 222 | names = _get_candidate_names() 223 | 224 | for seq in xrange(TMP_MAX): 225 | name = names.next() 226 | file = _os.path.join(dir, pre + name + suf) 227 | try: 228 | fd = _os.open(file, flags, 0600) 229 | _set_cloexec(fd) 230 | return (fd, _os.path.abspath(file)) 231 | except OSError, e: 232 | if e.errno == _errno.EEXIST: 233 | continue # try again 234 | raise 235 | 236 | raise IOError, (_errno.EEXIST, "No usable temporary file name found") 237 | 238 | 239 | # User visible interfaces. 240 | 241 | def gettempprefix(): 242 | """Accessor for tempdir.template.""" 243 | return template 244 | 245 | tempdir = None 246 | 247 | def gettempdir(): 248 | """Accessor for tempfile.tempdir.""" 249 | global tempdir 250 | if tempdir is None: 251 | _once_lock.acquire() 252 | try: 253 | if tempdir is None: 254 | tempdir = _get_default_tempdir() 255 | finally: 256 | _once_lock.release() 257 | return tempdir 258 | 259 | def mkstemp(suffix="", prefix=template, dir=None, text=False): 260 | """User-callable function to create and return a unique temporary 261 | file. The return value is a pair (fd, name) where fd is the 262 | file descriptor returned by os.open, and name is the filename. 263 | 264 | If 'suffix' is specified, the file name will end with that suffix, 265 | otherwise there will be no suffix. 266 | 267 | If 'prefix' is specified, the file name will begin with that prefix, 268 | otherwise a default prefix is used. 269 | 270 | If 'dir' is specified, the file will be created in that directory, 271 | otherwise a default directory is used. 272 | 273 | If 'text' is specified and true, the file is opened in text 274 | mode. Else (the default) the file is opened in binary mode. On 275 | some operating systems, this makes no difference. 276 | 277 | The file is readable and writable only by the creating user ID. 278 | If the operating system uses permission bits to indicate whether a 279 | file is executable, the file is executable by no one. The file 280 | descriptor is not inherited by children of this process. 281 | 282 | Caller is responsible for deleting the file when done with it. 283 | """ 284 | 285 | if dir is None: 286 | dir = gettempdir() 287 | 288 | if text: 289 | flags = _text_openflags 290 | else: 291 | flags = _bin_openflags 292 | 293 | return _mkstemp_inner(dir, prefix, suffix, flags) 294 | 295 | 296 | def mkdtemp(suffix="", prefix=template, dir=None): 297 | """User-callable function to create and return a unique temporary 298 | directory. The return value is the pathname of the directory. 299 | 300 | Arguments are as for mkstemp, except that the 'text' argument is 301 | not accepted. 302 | 303 | The directory is readable, writable, and searchable only by the 304 | creating user. 305 | 306 | Caller is responsible for deleting the directory when done with it. 307 | """ 308 | 309 | if dir is None: 310 | dir = gettempdir() 311 | 312 | names = _get_candidate_names() 313 | 314 | for seq in xrange(TMP_MAX): 315 | name = names.next() 316 | file = _os.path.join(dir, prefix + name + suffix) 317 | try: 318 | _os.mkdir(file, 0700) 319 | return file 320 | except OSError, e: 321 | if e.errno == _errno.EEXIST: 322 | continue # try again 323 | raise 324 | 325 | raise IOError, (_errno.EEXIST, "No usable temporary directory name found") 326 | 327 | def mktemp(suffix="", prefix=template, dir=None): 328 | """User-callable function to return a unique temporary file name. The 329 | file is not created. 330 | 331 | Arguments are as for mkstemp, except that the 'text' argument is 332 | not accepted. 333 | 334 | This function is unsafe and should not be used. The file name 335 | refers to a file that did not exist at some point, but by the time 336 | you get around to creating it, someone else may have beaten you to 337 | the punch. 338 | """ 339 | 340 | ## from warnings import warn as _warn 341 | ## _warn("mktemp is a potential security risk to your program", 342 | ## RuntimeWarning, stacklevel=2) 343 | 344 | if dir is None: 345 | dir = gettempdir() 346 | 347 | names = _get_candidate_names() 348 | for seq in xrange(TMP_MAX): 349 | name = names.next() 350 | file = _os.path.join(dir, prefix + name + suffix) 351 | if not _exists(file): 352 | return file 353 | 354 | raise IOError, (_errno.EEXIST, "No usable temporary filename found") 355 | 356 | 357 | class _TemporaryFileWrapper: 358 | """Temporary file wrapper 359 | 360 | This class provides a wrapper around files opened for 361 | temporary use. In particular, it seeks to automatically 362 | remove the file when it is no longer needed. 363 | """ 364 | 365 | def __init__(self, file, name, delete=True): 366 | self.file = file 367 | self.name = name 368 | self.close_called = False 369 | self.delete = delete 370 | 371 | def __getattr__(self, name): 372 | # Attribute lookups are delegated to the underlying file 373 | # and cached for non-numeric results 374 | # (i.e. methods are cached, closed and friends are not) 375 | file = self.__dict__['file'] 376 | a = getattr(file, name) 377 | if not issubclass(type(a), type(0)): 378 | setattr(self, name, a) 379 | return a 380 | 381 | # The underlying __enter__ method returns the wrong object 382 | # (self.file) so override it to return the wrapper 383 | def __enter__(self): 384 | self.file.__enter__() 385 | return self 386 | 387 | # NT provides delete-on-close as a primitive, so we don't need 388 | # the wrapper to do anything special. We still use it so that 389 | # file.name is useful (i.e. not "(fdopen)") with NamedTemporaryFile. 390 | if _os.name != 'nt': 391 | # Cache the unlinker so we don't get spurious errors at 392 | # shutdown when the module-level "os" is None'd out. Note 393 | # that this must be referenced as self.unlink, because the 394 | # name TemporaryFileWrapper may also get None'd out before 395 | # __del__ is called. 396 | unlink = _os.unlink 397 | 398 | def close(self): 399 | if not self.close_called: 400 | self.close_called = True 401 | self.file.close() 402 | if self.delete: 403 | self.unlink(self.name) 404 | 405 | def __del__(self): 406 | self.close() 407 | 408 | # Need to trap __exit__ as well to ensure the file gets 409 | # deleted when used in a with statement 410 | def __exit__(self, exc, value, tb): 411 | result = self.file.__exit__(exc, value, tb) 412 | self.close() 413 | return result 414 | else: 415 | def __exit__(self, exc, value, tb): 416 | self.file.__exit__(exc, value, tb) 417 | 418 | 419 | def NamedTemporaryFile(mode='w+b', bufsize=-1, suffix="", 420 | prefix=template, dir=None, delete=True): 421 | """Create and return a temporary file. 422 | Arguments: 423 | 'prefix', 'suffix', 'dir' -- as for mkstemp. 424 | 'mode' -- the mode argument to os.fdopen (default "w+b"). 425 | 'bufsize' -- the buffer size argument to os.fdopen (default -1). 426 | 'delete' -- whether the file is deleted on close (default True). 427 | The file is created as mkstemp() would do it. 428 | 429 | Returns an object with a file-like interface; the name of the file 430 | is accessible as file.name. The file will be automatically deleted 431 | when it is closed unless the 'delete' argument is set to False. 432 | """ 433 | 434 | if dir is None: 435 | dir = gettempdir() 436 | 437 | if 'b' in mode: 438 | flags = _bin_openflags 439 | else: 440 | flags = _text_openflags 441 | 442 | # Setting O_TEMPORARY in the flags causes the OS to delete 443 | # the file when it is closed. This is only supported by Windows. 444 | if _os.name == 'nt' and delete: 445 | flags |= _os.O_TEMPORARY 446 | 447 | (fd, name) = _mkstemp_inner(dir, prefix, suffix, flags) 448 | file = _os.fdopen(fd, mode, bufsize) 449 | return _TemporaryFileWrapper(file, name, delete) 450 | 451 | if _os.name != 'posix' or _os.sys.platform == 'cygwin': 452 | # On non-POSIX and Cygwin systems, assume that we cannot unlink a file 453 | # while it is open. 454 | TemporaryFile = NamedTemporaryFile 455 | 456 | else: 457 | def TemporaryFile(mode='w+b', bufsize=-1, suffix="", 458 | prefix=template, dir=None): 459 | """Create and return a temporary file. 460 | Arguments: 461 | 'prefix', 'suffix', 'dir' -- as for mkstemp. 462 | 'mode' -- the mode argument to os.fdopen (default "w+b"). 463 | 'bufsize' -- the buffer size argument to os.fdopen (default -1). 464 | The file is created as mkstemp() would do it. 465 | 466 | Returns an object with a file-like interface. The file has no 467 | name, and will cease to exist when it is closed. 468 | """ 469 | 470 | if dir is None: 471 | dir = gettempdir() 472 | 473 | if 'b' in mode: 474 | flags = _bin_openflags 475 | else: 476 | flags = _text_openflags 477 | 478 | (fd, name) = _mkstemp_inner(dir, prefix, suffix, flags) 479 | try: 480 | _os.unlink(name) 481 | return _os.fdopen(fd, mode, bufsize) 482 | except: 483 | _os.close(fd) 484 | raise 485 | 486 | class SpooledTemporaryFile: 487 | """Temporary file wrapper, specialized to switch from 488 | StringIO to a real file when it exceeds a certain size or 489 | when a fileno is needed. 490 | """ 491 | _rolled = False 492 | 493 | def __init__(self, max_size=0, mode='w+b', bufsize=-1, 494 | suffix="", prefix=template, dir=None): 495 | self._file = _StringIO() 496 | self._max_size = max_size 497 | self._rolled = False 498 | self._TemporaryFileArgs = (mode, bufsize, suffix, prefix, dir) 499 | 500 | def _check(self, file): 501 | if self._rolled: return 502 | max_size = self._max_size 503 | if max_size and file.tell() > max_size: 504 | self.rollover() 505 | 506 | def rollover(self): 507 | if self._rolled: return 508 | file = self._file 509 | newfile = self._file = TemporaryFile(*self._TemporaryFileArgs) 510 | del self._TemporaryFileArgs 511 | 512 | newfile.write(file.getvalue()) 513 | newfile.seek(file.tell(), 0) 514 | 515 | self._rolled = True 516 | 517 | # The method caching trick from NamedTemporaryFile 518 | # won't work here, because _file may change from a 519 | # _StringIO instance to a real file. So we list 520 | # all the methods directly. 521 | 522 | # Context management protocol 523 | def __enter__(self): 524 | if self._file.closed: 525 | raise ValueError("Cannot enter context with closed file") 526 | return self 527 | 528 | def __exit__(self, exc, value, tb): 529 | self._file.close() 530 | 531 | # file protocol 532 | def __iter__(self): 533 | return self._file.__iter__() 534 | 535 | def close(self): 536 | self._file.close() 537 | 538 | @property 539 | def closed(self): 540 | return self._file.closed 541 | 542 | @property 543 | def encoding(self): 544 | return self._file.encoding 545 | 546 | def fileno(self): 547 | self.rollover() 548 | return self._file.fileno() 549 | 550 | def flush(self): 551 | self._file.flush() 552 | 553 | def isatty(self): 554 | return self._file.isatty() 555 | 556 | @property 557 | def mode(self): 558 | return self._file.mode 559 | 560 | @property 561 | def name(self): 562 | return self._file.name 563 | 564 | @property 565 | def newlines(self): 566 | return self._file.newlines 567 | 568 | def next(self): 569 | return self._file.next 570 | 571 | def read(self, *args): 572 | return self._file.read(*args) 573 | 574 | def readline(self, *args): 575 | return self._file.readline(*args) 576 | 577 | def readlines(self, *args): 578 | return self._file.readlines(*args) 579 | 580 | def seek(self, *args): 581 | self._file.seek(*args) 582 | 583 | @property 584 | def softspace(self): 585 | return self._file.softspace 586 | 587 | def tell(self): 588 | return self._file.tell() 589 | 590 | def truncate(self): 591 | self._file.truncate() 592 | 593 | def write(self, s): 594 | file = self._file 595 | rv = file.write(s) 596 | self._check(file) 597 | return rv 598 | 599 | def writelines(self, iterable): 600 | file = self._file 601 | rv = file.writelines(iterable) 602 | self._check(file) 603 | return rv 604 | 605 | def xreadlines(self, *args): 606 | return self._file.xreadlines(*args) 607 | -------------------------------------------------------------------------------- /compute-engine/bookshelf/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | 17 | from flask import current_app, Flask, redirect, url_for 18 | 19 | 20 | def create_app(config, debug=False, testing=False, config_overrides=None): 21 | app = Flask(__name__) 22 | app.config.from_object(config) 23 | 24 | app.debug = debug 25 | app.testing = testing 26 | 27 | if config_overrides: 28 | app.config.update(config_overrides) 29 | 30 | # Configure logging 31 | if not app.testing: 32 | logging.basicConfig(level=logging.INFO) 33 | 34 | # Setup the data model. 35 | with app.app_context(): 36 | model = get_model() 37 | model.init_app(app) 38 | 39 | # Register the Bookshelf CRUD blueprint. 40 | from .crud import crud 41 | app.register_blueprint(crud, url_prefix='/books') 42 | 43 | # Add a default root route. 44 | @app.route("/") 45 | def index(): 46 | return redirect(url_for('crud.list')) 47 | 48 | # Add an error handler. This is useful for debugging the live application, 49 | # however, you should disable the output of the exception for production 50 | # applications. 51 | @app.errorhandler(500) 52 | def server_error(e): 53 | return """ 54 | An internal error occurred:
{}
55 | See logs for full stacktrace. 56 | """.format(e), 500 57 | 58 | return app 59 | 60 | 61 | def get_model(): 62 | model_backend = current_app.config['DATA_BACKEND'] 63 | if model_backend == 'cloudsql': 64 | from . import model_cloudsql 65 | model = model_cloudsql 66 | elif model_backend == 'datastore': 67 | from . import model_datastore 68 | model = model_datastore 69 | else: 70 | raise ValueError( 71 | "No appropriate databackend configured. " 72 | "Please specify datastore, or cloudsql") 73 | 74 | return model 75 | 76 | -------------------------------------------------------------------------------- /compute-engine/bookshelf/crud.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from bookshelf import get_model, storage 16 | from flask import Blueprint, current_app, redirect, render_template, request, \ 17 | url_for 18 | 19 | 20 | crud = Blueprint('crud', __name__) 21 | 22 | 23 | # [START upload_image_file] 24 | def upload_image_file(file): 25 | """ 26 | Upload the user-uploaded file to Google Cloud Storage and retrieve its 27 | publicly-accessible URL. 28 | """ 29 | if not file: 30 | return None 31 | 32 | public_url = storage.upload_file( 33 | file.read(), 34 | file.filename, 35 | file.content_type 36 | ) 37 | 38 | current_app.logger.info( 39 | "Uploaded file %s as %s.", file.filename, public_url) 40 | 41 | return public_url 42 | # [END upload_image_file] 43 | 44 | 45 | @crud.route("/") 46 | def list(): 47 | token = request.args.get('page_token', None) 48 | if token: 49 | token = token.encode('utf-8') 50 | 51 | books, next_page_token = get_model().list(cursor=token) 52 | 53 | return render_template( 54 | "list.html", 55 | books=books, 56 | next_page_token=next_page_token) 57 | 58 | 59 | @crud.route('/') 60 | def view(id): 61 | book = get_model().read(id) 62 | return render_template("view.html", book=book) 63 | 64 | 65 | @crud.route('/add', methods=['GET', 'POST']) 66 | def add(): 67 | if request.method == 'POST': 68 | data = request.form.to_dict(flat=True) 69 | 70 | # If an image was uploaded, update the data to point to the new image. 71 | # [START image_url] 72 | image_url = upload_image_file(request.files.get('image')) 73 | # [END image_url] 74 | 75 | # [START image_url2] 76 | if image_url: 77 | data['imageUrl'] = image_url 78 | # [END image_url2] 79 | 80 | book = get_model().create(data) 81 | 82 | return redirect(url_for('.view', id=book['id'])) 83 | 84 | return render_template("form.html", action="Add", book={}) 85 | 86 | 87 | @crud.route('//edit', methods=['GET', 'POST']) 88 | def edit(id): 89 | book = get_model().read(id) 90 | 91 | if request.method == 'POST': 92 | data = request.form.to_dict(flat=True) 93 | 94 | image_url = upload_image_file(request.files.get('image')) 95 | 96 | if image_url: 97 | data['imageUrl'] = image_url 98 | 99 | book = get_model().update(data, id) 100 | 101 | return redirect(url_for('.view', id=book['id'])) 102 | 103 | return render_template("form.html", action="Edit", book=book) 104 | 105 | 106 | @crud.route('//delete') 107 | def delete(id): 108 | get_model().delete(id) 109 | return redirect(url_for('.list')) 110 | -------------------------------------------------------------------------------- /compute-engine/bookshelf/model_cloudsql.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from flask import Flask 16 | from flask.ext.sqlalchemy import SQLAlchemy 17 | 18 | 19 | builtin_list = list 20 | 21 | 22 | db = SQLAlchemy() 23 | 24 | 25 | def init_app(app): 26 | db.init_app(app) 27 | 28 | 29 | def from_sql(row): 30 | """Translates a SQLAlchemy model instance into a dictionary""" 31 | data = row.__dict__.copy() 32 | data['id'] = row.id 33 | data.pop('_sa_instance_state') 34 | return data 35 | 36 | 37 | # [START model] 38 | class Book(db.Model): 39 | __tablename__ = 'books' 40 | 41 | id = db.Column(db.Integer, primary_key=True) 42 | title = db.Column(db.String(255)) 43 | author = db.Column(db.String(255)) 44 | publishedDate = db.Column(db.String(255)) 45 | imageUrl = db.Column(db.String(255)) 46 | description = db.Column(db.String(255)) 47 | createdBy = db.Column(db.String(255)) 48 | createdById = db.Column(db.String(255)) 49 | 50 | def __repr__(self): 51 | return " 17 | 18 | 19 | Bookshelf - Python on Google Cloud Platform 20 | 21 | 22 | 23 | 24 | 25 | 35 |
36 | {% block content %}{% endblock %} 37 |
38 | {{user}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /compute-engine/bookshelf/templates/form.html: -------------------------------------------------------------------------------- 1 | {# 2 | # Copyright 2016 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | #} 16 | 17 | {# [START form] #} 18 | {% extends "base.html" %} 19 | 20 | {% block content %} 21 |

{{action}} book

22 | 23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 | 38 |
39 | 40 |
41 | 42 | 43 |
44 | 45 |
46 | 47 | 48 |
49 | 50 | 54 | 55 | 56 |
57 | 58 | {% endblock %} 59 | {# [END form] #} 60 | -------------------------------------------------------------------------------- /compute-engine/bookshelf/templates/list.html: -------------------------------------------------------------------------------- 1 | {# 2 | # Copyright 2016 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | #} 16 | 17 | {% extends "base.html" %} 18 | 19 | {% block content %} 20 | 21 |

Books

22 | 23 | 24 | Add book 25 | 26 | 27 | {% for book in books %} 28 | 43 | {% else %} 44 |

No books found

45 | {% endfor %} 46 | 47 | {% if next_page_token %} 48 | 53 | {% endif %} 54 | 55 | {% endblock %} 56 | -------------------------------------------------------------------------------- /compute-engine/bookshelf/templates/view.html: -------------------------------------------------------------------------------- 1 | {# 2 | # Copyright 2016 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | #} 16 | 17 | {% extends "base.html" %} 18 | 19 | {% block content %} 20 | 21 |

Book

22 | 23 | 33 | 34 |
35 | {# [START book_image] #} 36 |
37 | {% if book.imageUrl %} 38 | 39 | {% else %} 40 | 41 | {% endif %} 42 |
43 | {# [END book_image] #} 44 |
45 |

46 | {{book.title}} 47 | {{book.publishedDate}} 48 |

49 |
By {{book.author|default('Unknown', True)}}
50 |

{{book.description}}

51 |
52 |
53 | 54 | {% endblock %} 55 | -------------------------------------------------------------------------------- /compute-engine/config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This file contains all of the configuration values for the application. 17 | Update this file with the values for your specific Google Cloud project. 18 | You can create and manage projects at https://console.developers.google.com 19 | """ 20 | 21 | # There are two different ways to store the data in the application. 22 | # You can choose 'datastore', or 'cloudsql'. Be sure to 23 | # configure the respective settings for the one you choose below. 24 | # You do not have to configure the other data backend. If unsure, choose 25 | # 'datastore' as it does not require any additional configuration. 26 | DATA_BACKEND = 'datastore' 27 | 28 | # Google Cloud Project ID. This can be found on the 'Overview' page at 29 | # https://console.developers.google.com 30 | PROJECT_ID = 'your-project-id' 31 | 32 | # SQLAlchemy configuration 33 | # Replace user, pass, host, and database with the respective values of your 34 | # Cloud SQL instance. 35 | SQLALCHEMY_DATABASE_URI = \ 36 | 'mysql+pymysql://user:password@host/database' 37 | 38 | # Google Cloud Storage and upload settings. 39 | # Typically, you'll name your bucket the same as your project. To create a 40 | # bucket: 41 | # 42 | # $ gsutil mb gs:// 43 | # 44 | # You also need to make sure that the default ACL is set to public-read, 45 | # otherwise users will not be able to see their upload images: 46 | # 47 | # $ gsutil defacl set public-read gs:// 48 | # 49 | # You can adjust the max content length and allow extensions settings to allow 50 | # larger or more varied file types if desired. 51 | CLOUD_STORAGE_BUCKET = PROJECT_ID 52 | MAX_CONTENT_LENGTH = 8 * 1024 * 1024 53 | ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif']) 54 | -------------------------------------------------------------------------------- /compute-engine/main.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import bookshelf 16 | import config 17 | 18 | 19 | app = bookshelf.create_app(config) 20 | 21 | 22 | # This is only used when running locally. When running live, gunicorn runs 23 | # the application. 24 | if __name__ == '__main__': 25 | app.run(host='127.0.0.1', port=8080, debug=True) 26 | -------------------------------------------------------------------------------- /compute-engine/procfile: -------------------------------------------------------------------------------- 1 | bookshelf: gunicorn -b 0.0.0.0:$PORT main:app 2 | -------------------------------------------------------------------------------- /compute-engine/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.11.1 2 | gcloud==0.18.1 3 | gunicorn==19.6.0 4 | six==1.10.0 5 | honcho==0.7.1 6 | -------------------------------------------------------------------------------- /compute-engine/startup-scripts/startup-script.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # Copyright 2016 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # [START startup] 17 | set -v 18 | 19 | # Talk to the metadata server to get the project id 20 | PROJECTID=$(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google") 21 | 22 | # Install logging monitor. The monitor will automatically pickup logs sent to 23 | # syslog. 24 | # [START logging] 25 | curl -s "https://storage.googleapis.com/signals-agents/logging/google-fluentd-install.sh" | bash 26 | service google-fluentd restart & 27 | # [END logging] 28 | 29 | # Install dependencies from apt 30 | apt-get update 31 | apt-get install -yq \ 32 | git build-essential supervisor python python-dev python-pip libffi-dev \ 33 | libssl-dev 34 | 35 | # Create a pythonapp user. The application will run as this user. 36 | useradd -m -d /home/pythonapp pythonapp 37 | 38 | # pip from apt is out of date, so make it update itself and install virtualenv. 39 | pip install --upgrade pip virtualenv 40 | 41 | # Get the source code from the Google Cloud Repository 42 | # git requires $HOME and it's not set during the startup script. 43 | export HOME=/root 44 | git config --global credential.helper gcloud.sh 45 | git clone https://source.developers.google.com/p/$PROJECTID /opt/app 46 | 47 | # Replace the project ID placeholder in config.py 48 | sed -i s/your-project-id/"$PROJECTID"/ /opt/app/compute-engine/config.py 49 | 50 | # Install app dependencies 51 | virtualenv /opt/app/compute-engine/env 52 | /opt/app/compute-engine/env/bin/pip install -r /opt/app/compute-engine/requirements.txt 53 | 54 | # Make sure the pythonapp user owns the application code 55 | chown -R pythonapp:pythonapp /opt/app 56 | 57 | # Configure supervisor to start gunicorn inside of our virtualenv and run the 58 | # application. 59 | cat >/etc/supervisor/conf.d/python-app.conf << EOF 60 | [program:pythonapp] 61 | directory=/opt/app/compute-engine 62 | command=/opt/app/compute-engine/env/bin/gunicorn main:app --bind 0.0.0.0:8080 63 | autostart=true 64 | autorestart=true 65 | user=pythonapp 66 | # Environment variables ensure that the application runs inside of the 67 | # configured virtualenv. 68 | environment=VIRTUAL_ENV="/opt/app/env/compute-engine",PATH="/opt/app/compute-engine/env/bin",\ 69 | HOME="/home/pythonapp",USER="pythonapp" 70 | stdout_logfile=syslog 71 | stderr_logfile=syslog 72 | EOF 73 | 74 | supervisorctl reread 75 | supervisorctl update 76 | 77 | # Application should now be running under supervisor 78 | # [END startup] 79 | -------------------------------------------------------------------------------- /container-engine/.dockerignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | *.pyo 4 | *.pyd 5 | .Python 6 | env 7 | pip-log.txt 8 | pip-delete-this-directory.txt 9 | .tox 10 | .coverage 11 | .coverage.* 12 | .cache 13 | nosetests.xml 14 | coverage.xml 15 | *,cover 16 | *.log 17 | .git 18 | -------------------------------------------------------------------------------- /container-engine/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License 14 | 15 | # The Google App Engine base image is debian (jessie) with ca-certificates 16 | # installed. 17 | FROM gcr.io/google_appengine/base 18 | 19 | # Install Python, pip, and C dev libraries necessary to compile the most popular 20 | # Python libraries. 21 | RUN apt-get -q update && \ 22 | apt-get install --no-install-recommends -y -q \ 23 | python2.7 python3.4 python2.7-dev python3.4-dev python-pip build-essential git mercurial \ 24 | libffi-dev libssl-dev libxml2-dev \ 25 | libxslt1-dev libpq-dev libmysqlclient-dev libcurl4-openssl-dev \ 26 | libjpeg-dev zlib1g-dev libpng12-dev \ 27 | gfortran libblas-dev liblapack-dev libatlas-dev libquadmath0 \ 28 | libfreetype6-dev pkg-config swig \ 29 | && \ 30 | apt-get clean && rm /var/lib/apt/lists/*_* 31 | 32 | # Setup locale. This prevents Python 3 IO encoding issues. 33 | ENV LANG C.UTF-8 34 | 35 | # Upgrade pip (debian package version tends to run a few version behind) and 36 | # install virtualenv system-wide. 37 | RUN pip install --upgrade pip virtualenv 38 | 39 | RUN ln -s /home/vmagent/app /app 40 | WORKDIR /app 41 | 42 | # Port 8080 is the port used by Google App Engine for serving HTTP traffic. 43 | EXPOSE 8080 44 | ENV PORT 8080 45 | 46 | # Create a virtualenv for the application dependencies. 47 | # If you want to use Python 3, add the -p python3.4 flag. 48 | RUN virtualenv /env 49 | 50 | # Set virtualenv environment variables. This is equivalent to running 51 | # source /env/bin/activate. This ensures the application is executed within 52 | # the context of the virtualenv and will have access to its dependencies. 53 | ENV VIRTUAL_ENV /env 54 | ENV PATH /env/bin:$PATH 55 | 56 | # Install dependencies. 57 | ADD requirements.txt /app/requirements.txt 58 | RUN pip install -r /app/requirements.txt 59 | 60 | # Add application code. 61 | ADD . /app 62 | 63 | # Instead of using gunicorn directly, we'll use Honcho. Honcho is a python port 64 | # of the Foreman process manager. $PROCESSES is set in the pod manifest 65 | # to control which processes Honcho will start. 66 | CMD honcho start -f /app/procfile $PROCESSES 67 | -------------------------------------------------------------------------------- /container-engine/bookshelf-frontend.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License 14 | 15 | # This file configures the bookshelf application frontend. The frontend serves 16 | # public web traffic. 17 | 18 | # The bookshelf frontend replication controller ensures that at least 3 19 | # instances of the bookshelf app are running on the cluster. 20 | # For more info about Pods see: 21 | # https://cloud.google.com/container-engine/docs/pods/ 22 | # For more info about Replication Controllers: 23 | # https://cloud.google.com/container-engine/docs/replicationcontrollers/ 24 | apiVersion: v1 25 | kind: ReplicationController 26 | metadata: 27 | name: bookshelf-frontend 28 | spec: 29 | replicas: 3 30 | template: 31 | metadata: 32 | labels: 33 | app: bookshelf 34 | tier: frontend 35 | spec: 36 | containers: 37 | - name: bookshelf-app 38 | # Replace $GCLOUD_PROJECT with your project ID or use `make template`. 39 | image: gcr.io/your-project-id/bookshelf 40 | # This setting makes nodes pull the docker image every time before 41 | # starting the pod. This is useful when debugging, but should be turned 42 | # off in production. 43 | imagePullPolicy: Always 44 | # The PROCESSES environment variable is used by Honcho in the 45 | # Dockerfile's CMD to control which processes are started. In this 46 | # case, only the bookshelf process is needed. 47 | env: 48 | - name: PROCESSES 49 | value: bookshelf 50 | # The bookshelf process listens on port 8080 for web traffic by default. 51 | ports: 52 | - containerPort: 8080 53 | 54 | --- 55 | 56 | # The bookshelf service provides a load-balancing proxy over the bookshelf app 57 | # pods. By specifying the type as a 'LoadBalancer', Container Engine will 58 | # create an external HTTP load balancer. 59 | # For more information about Services see: 60 | # https://cloud.google.com/container-engine/docs/services/ 61 | # For more information about external HTTP load balancing see: 62 | # https://cloud.google.com/container-engine/docs/load-balancer 63 | apiVersion: v1 64 | kind: Service 65 | metadata: 66 | name: bookshelf-frontend 67 | labels: 68 | app: bookshelf 69 | tier: frontend 70 | spec: 71 | type: LoadBalancer 72 | ports: 73 | - port: 80 74 | targetPort: 8080 75 | selector: 76 | app: bookshelf 77 | tier: frontend 78 | -------------------------------------------------------------------------------- /container-engine/bookshelf/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | 17 | from flask import current_app, Flask, redirect, url_for 18 | 19 | 20 | def create_app(config, debug=False, testing=False, config_overrides=None): 21 | app = Flask(__name__) 22 | app.config.from_object(config) 23 | 24 | app.debug = debug 25 | app.testing = testing 26 | 27 | if config_overrides: 28 | app.config.update(config_overrides) 29 | 30 | # Configure logging 31 | if not app.testing: 32 | logging.basicConfig(level=logging.INFO) 33 | 34 | # Setup the data model. 35 | with app.app_context(): 36 | model = get_model() 37 | model.init_app(app) 38 | 39 | # Register the Bookshelf CRUD blueprint. 40 | from .crud import crud 41 | app.register_blueprint(crud, url_prefix='/books') 42 | 43 | # Add a default root route. 44 | @app.route("/") 45 | def index(): 46 | return redirect(url_for('crud.list')) 47 | 48 | # Add an error handler. This is useful for debugging the live application, 49 | # however, you should disable the output of the exception for production 50 | # applications. 51 | @app.errorhandler(500) 52 | def server_error(e): 53 | return """ 54 | An internal error occurred:
{}
55 | See logs for full stacktrace. 56 | """.format(e), 500 57 | 58 | return app 59 | 60 | 61 | def get_model(): 62 | model_backend = current_app.config['DATA_BACKEND'] 63 | if model_backend == 'cloudsql': 64 | from . import model_cloudsql 65 | model = model_cloudsql 66 | elif model_backend == 'datastore': 67 | from . import model_datastore 68 | model = model_datastore 69 | else: 70 | raise ValueError( 71 | "No appropriate databackend configured. " 72 | "Please specify datastore, or cloudsql") 73 | 74 | return model 75 | 76 | -------------------------------------------------------------------------------- /container-engine/bookshelf/crud.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from bookshelf import get_model, storage 16 | from flask import Blueprint, current_app, redirect, render_template, request, \ 17 | url_for 18 | 19 | 20 | crud = Blueprint('crud', __name__) 21 | 22 | 23 | # [START upload_image_file] 24 | def upload_image_file(file): 25 | """ 26 | Upload the user-uploaded file to Google Cloud Storage and retrieve its 27 | publicly-accessible URL. 28 | """ 29 | if not file: 30 | return None 31 | 32 | public_url = storage.upload_file( 33 | file.read(), 34 | file.filename, 35 | file.content_type 36 | ) 37 | 38 | current_app.logger.info( 39 | "Uploaded file %s as %s.", file.filename, public_url) 40 | 41 | return public_url 42 | # [END upload_image_file] 43 | 44 | 45 | @crud.route("/") 46 | def list(): 47 | token = request.args.get('page_token', None) 48 | if token: 49 | token = token.encode('utf-8') 50 | 51 | books, next_page_token = get_model().list(cursor=token) 52 | 53 | return render_template( 54 | "list.html", 55 | books=books, 56 | next_page_token=next_page_token) 57 | 58 | 59 | @crud.route('/') 60 | def view(id): 61 | book = get_model().read(id) 62 | return render_template("view.html", book=book) 63 | 64 | 65 | @crud.route('/add', methods=['GET', 'POST']) 66 | def add(): 67 | if request.method == 'POST': 68 | data = request.form.to_dict(flat=True) 69 | 70 | # If an image was uploaded, update the data to point to the new image. 71 | # [START image_url] 72 | image_url = upload_image_file(request.files.get('image')) 73 | # [END image_url] 74 | 75 | # [START image_url2] 76 | if image_url: 77 | data['imageUrl'] = image_url 78 | # [END image_url2] 79 | 80 | book = get_model().create(data) 81 | 82 | return redirect(url_for('.view', id=book['id'])) 83 | 84 | return render_template("form.html", action="Add", book={}) 85 | 86 | 87 | @crud.route('//edit', methods=['GET', 'POST']) 88 | def edit(id): 89 | book = get_model().read(id) 90 | 91 | if request.method == 'POST': 92 | data = request.form.to_dict(flat=True) 93 | 94 | image_url = upload_image_file(request.files.get('image')) 95 | 96 | if image_url: 97 | data['imageUrl'] = image_url 98 | 99 | book = get_model().update(data, id) 100 | 101 | return redirect(url_for('.view', id=book['id'])) 102 | 103 | return render_template("form.html", action="Edit", book=book) 104 | 105 | 106 | @crud.route('//delete') 107 | def delete(id): 108 | get_model().delete(id) 109 | return redirect(url_for('.list')) 110 | -------------------------------------------------------------------------------- /container-engine/bookshelf/model_cloudsql.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from flask import Flask 16 | from flask.ext.sqlalchemy import SQLAlchemy 17 | 18 | 19 | builtin_list = list 20 | 21 | 22 | db = SQLAlchemy() 23 | 24 | 25 | def init_app(app): 26 | db.init_app(app) 27 | 28 | 29 | def from_sql(row): 30 | """Translates a SQLAlchemy model instance into a dictionary""" 31 | data = row.__dict__.copy() 32 | data['id'] = row.id 33 | data.pop('_sa_instance_state') 34 | return data 35 | 36 | 37 | # [START model] 38 | class Book(db.Model): 39 | __tablename__ = 'books' 40 | 41 | id = db.Column(db.Integer, primary_key=True) 42 | title = db.Column(db.String(255)) 43 | author = db.Column(db.String(255)) 44 | publishedDate = db.Column(db.String(255)) 45 | imageUrl = db.Column(db.String(255)) 46 | description = db.Column(db.String(255)) 47 | createdBy = db.Column(db.String(255)) 48 | createdById = db.Column(db.String(255)) 49 | 50 | def __repr__(self): 51 | return " 17 | 18 | 19 | Bookshelf - Python on Google Cloud Platform 20 | 21 | 22 | 23 | 24 | 25 | 35 |
36 | {% block content %}{% endblock %} 37 |
38 | {{user}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /container-engine/bookshelf/templates/form.html: -------------------------------------------------------------------------------- 1 | {# 2 | # Copyright 2016 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | #} 16 | 17 | {# [START form] #} 18 | {% extends "base.html" %} 19 | 20 | {% block content %} 21 |

{{action}} book

22 | 23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 | 38 |
39 | 40 |
41 | 42 | 43 |
44 | 45 |
46 | 47 | 48 |
49 | 50 | 54 | 55 | 56 |
57 | 58 | {% endblock %} 59 | {# [END form] #} 60 | -------------------------------------------------------------------------------- /container-engine/bookshelf/templates/list.html: -------------------------------------------------------------------------------- 1 | {# 2 | # Copyright 2016 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | #} 16 | 17 | {% extends "base.html" %} 18 | 19 | {% block content %} 20 | 21 |

Books

22 | 23 | 24 | Add book 25 | 26 | 27 | {% for book in books %} 28 | 43 | {% else %} 44 |

No books found

45 | {% endfor %} 46 | 47 | {% if next_page_token %} 48 | 53 | {% endif %} 54 | 55 | {% endblock %} 56 | -------------------------------------------------------------------------------- /container-engine/bookshelf/templates/view.html: -------------------------------------------------------------------------------- 1 | {# 2 | # Copyright 2016 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | #} 16 | 17 | {% extends "base.html" %} 18 | 19 | {% block content %} 20 | 21 |

Book

22 | 23 | 33 | 34 |
35 | {# [START book_image] #} 36 |
37 | {% if book.imageUrl %} 38 | 39 | {% else %} 40 | 41 | {% endif %} 42 |
43 | {# [END book_image] #} 44 |
45 |

46 | {{book.title}} 47 | {{book.publishedDate}} 48 |

49 |
By {{book.author|default('Unknown', True)}}
50 |

{{book.description}}

51 |
52 |
53 | 54 | {% endblock %} 55 | -------------------------------------------------------------------------------- /container-engine/config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This file contains all of the configuration values for the application. 17 | Update this file with the values for your specific Google Cloud project. 18 | You can create and manage projects at https://console.developers.google.com 19 | """ 20 | 21 | # There are two different ways to store the data in the application. 22 | # You can choose 'datastore', or 'cloudsql'. Be sure to 23 | # configure the respective settings for the one you choose below. 24 | # You do not have to configure the other data backend. If unsure, choose 25 | # 'datastore' as it does not require any additional configuration. 26 | DATA_BACKEND = 'datastore' 27 | 28 | # Google Cloud Project ID. This can be found on the 'Overview' page at 29 | # https://console.developers.google.com 30 | PROJECT_ID = 'your-project-id' 31 | 32 | # SQLAlchemy configuration 33 | # Replace user, pass, host, and database with the respective values of your 34 | # Cloud SQL instance. 35 | SQLALCHEMY_DATABASE_URI = \ 36 | 'mysql+pymysql://user:password@host/database' 37 | 38 | # Google Cloud Storage and upload settings. 39 | # Typically, you'll name your bucket the same as your project. To create a 40 | # bucket: 41 | # 42 | # $ gsutil mb gs:// 43 | # 44 | # You also need to make sure that the default ACL is set to public-read, 45 | # otherwise users will not be able to see their upload images: 46 | # 47 | # $ gsutil defacl set public-read gs:// 48 | # 49 | # You can adjust the max content length and allow extensions settings to allow 50 | # larger or more varied file types if desired. 51 | CLOUD_STORAGE_BUCKET = PROJECT_ID 52 | MAX_CONTENT_LENGTH = 8 * 1024 * 1024 53 | ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif']) 54 | -------------------------------------------------------------------------------- /container-engine/main.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import bookshelf 16 | import config 17 | 18 | 19 | # Note: debug=True is enabled here to help with troubleshooting. You should 20 | # remove this in production. 21 | app = bookshelf.create_app(config, debug=True) 22 | 23 | 24 | # This is only used when running locally. When running live, gunicorn runs 25 | # the application. 26 | if __name__ == '__main__': 27 | app.run(host='127.0.0.1', port=8080, debug=True) 28 | -------------------------------------------------------------------------------- /container-engine/procfile: -------------------------------------------------------------------------------- 1 | bookshelf: gunicorn -b 0.0.0.0:$PORT main:app 2 | -------------------------------------------------------------------------------- /container-engine/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.11.1 2 | protobuf==3.0.0 3 | gcloud==0.18.1 4 | gunicorn==19.6.0 5 | six==1.10.0 6 | honcho==0.7.1 7 | --------------------------------------------------------------------------------