├── .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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------