diff --git a/appengine/standard_python37/cloudsql/app.yaml b/appengine/standard_python37/cloudsql/app.yaml new file mode 100644 index 000000000000..a16f999de9ac --- /dev/null +++ b/appengine/standard_python37/cloudsql/app.yaml @@ -0,0 +1,9 @@ +# [START gae_python37_cloudsql_config] +runtime: python37 + +env_variables: + CLOUDSQL_USERNAME: YOUR-USERNAME + CLOUDSQL_PASSWORD: YOUR-PASSWORD + CLOUDSQL_DATABASE_NAME: YOUR-DATABASE + CLOUDSQL_CONNECTION_NAME: YOUR-CONNECTION-NAME +# [END gae_python37_cloudsql_config] diff --git a/appengine/standard_python37/cloudsql/main_mysql.py b/appengine/standard_python37/cloudsql/main_mysql.py new file mode 100644 index 000000000000..7f0082c9db31 --- /dev/null +++ b/appengine/standard_python37/cloudsql/main_mysql.py @@ -0,0 +1,56 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START gae_python37_cloudsql_mysql] +import os + +from flask import Flask +import pymysql + +db_user = os.environ.get('CLOUD_SQL_USERNAME') +db_password = os.environ.get('CLOUD_SQL_PASSWORD') +db_name = os.environ.get('CLOUD_SQL_DATABASE_NAME') +db_connection_name = os.environ.get('CLOUD_SQL_CONNECTION_NAME') + +app = Flask(__name__) + + +@app.route('/') +def main(): + # When deployed to App Engine, the `GAE_ENV` environment variable will be + # set to `standard` + if os.environ.get('GAE_ENV'): + # If deployed, use the local socket interface for accessing Cloud SQL + host = '/cloudsql/{}'.format(db_connection_name) + else: + # If running locally, use the TCP connections instead + # Set up Cloud SQL Proxy (cloud.google.com/sql/docs/mysql/sql-proxy) + # so that your application can use 127.0.0.1:3306 to connect to your + # Cloud SQL instance + host = '127.0.0.1' + + cnx = pymysql.connect(user=db_user, password=db_password, + host=host, db=db_name) + with cnx.cursor() as cursor: + cursor.execute('SELECT NOW() as now;') + result = cursor.fetchall() + current_time = result[0][0] + cnx.close() + + return str(current_time) +# [END gae_python37_cloudsql_mysql] + + +if __name__ == '__main__': + app.run(host='127.0.0.1', port=8080, debug=True) diff --git a/appengine/standard_python37/cloudsql/main_mysql_pooling.py b/appengine/standard_python37/cloudsql/main_mysql_pooling.py new file mode 100644 index 000000000000..6002eb9fb8ac --- /dev/null +++ b/appengine/standard_python37/cloudsql/main_mysql_pooling.py @@ -0,0 +1,63 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START gae_python37_cloudsql_mysql_pooling] +import os + +from flask import Flask +import sqlalchemy + +db_user = os.environ.get('CLOUD_SQL_USERNAME') +db_password = os.environ.get('CLOUD_SQL_PASSWORD') +db_name = os.environ.get('CLOUD_SQL_DATABASE_NAME') +db_connection_name = os.environ.get('CLOUD_SQL_CONNECTION_NAME') + +# When deployed to App Engine, the `GAE_ENV` environment variable will be +# set to `standard` +if os.environ.get('GAE_ENV'): + # If deployed, use the local socket interface for accessing Cloud SQL + host = '/cloudsql/{}'.format(db_connection_name) +else: + # If running locally, use the TCP connections instead + # Set up Cloud SQL Proxy (cloud.google.com/sql/docs/mysql/sql-proxy) + # so that your application can use 127.0.0.1:3306 to connect to your + # Cloud SQL instance + host = '127.0.0.1' + +# The Engine object returned by create_engine() has a QueuePool integrated +# See https://docs.sqlalchemy.org/en/latest/core/pooling.html for more +# information +engine = sqlalchemy.create_engine('mysql+pymysql://{}:{}@{}/{}'.format( + db_user, db_password, host, db_name +), pool_size=3) + +app = Flask(__name__) + + +@app.route('/') +def main(): + cnx = engine.connect() + cursor = cnx.execute('SELECT NOW() as now;') + result = cursor.fetchall() + current_time = result[0][0] + # If the connection comes from a pool, close() will send the connection + # back to the pool instead of closing it + cnx.close() + + return str(current_time) +# [END gae_python37_cloudsql_mysql_pooling] + + +if __name__ == '__main__': + app.run(host='127.0.0.1', port=8080, debug=True) diff --git a/appengine/standard_python37/cloudsql/main_postgres.py b/appengine/standard_python37/cloudsql/main_postgres.py new file mode 100644 index 000000000000..91c663731363 --- /dev/null +++ b/appengine/standard_python37/cloudsql/main_postgres.py @@ -0,0 +1,57 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START gae_python37_cloudsql_psql] +import os + +from flask import Flask +import psycopg2 + +db_user = os.environ.get('CLOUD_SQL_USERNAME') +db_password = os.environ.get('CLOUD_SQL_PASSWORD') +db_name = os.environ.get('CLOUD_SQL_DATABASE_NAME') +db_connection_name = os.environ.get('CLOUD_SQL_CONNECTION_NAME') + +app = Flask(__name__) + + +@app.route('/') +def main(): + # When deployed to App Engine, the `GAE_ENV` environment variable will be + # set to `standard` + if os.environ.get('GAE_ENV'): + # If deployed, use the local socket interface for accessing Cloud SQL + host = '/cloudsql/{}'.format(db_connection_name) + else: + # If running locally, use the TCP connections instead + # Set up Cloud SQL Proxy (cloud.google.com/sql/docs/mysql/sql-proxy) + # so that your application can use 127.0.0.1:3306 to connect to your + # Cloud SQL instance + host = '127.0.0.1' + + cnx = psycopg2.connect(dbname=db_name, user=db_user, + password=db_password, host=host) + with cnx.cursor() as cursor: + cursor.execute('SELECT NOW() as now;') + result = cursor.fetchall() + current_time = result[0][0] + cnx.commit() + cnx.close() + + return str(current_time) +# [END gae_python37_cloudsql_psql] + + +if __name__ == '__main__': + app.run(host='127.0.0.1', port=8080, debug=True) diff --git a/appengine/standard_python37/cloudsql/main_postgres_pooling.py b/appengine/standard_python37/cloudsql/main_postgres_pooling.py new file mode 100644 index 000000000000..b1e85de862fb --- /dev/null +++ b/appengine/standard_python37/cloudsql/main_postgres_pooling.py @@ -0,0 +1,66 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START gae_python37_cloudsql_psql_pooling] +import os + +from flask import Flask +import psycopg2.pool + +db_user = os.environ.get('CLOUD_SQL_USERNAME') +db_password = os.environ.get('CLOUD_SQL_PASSWORD') +db_name = os.environ.get('CLOUD_SQL_DATABASE_NAME') +db_connection_name = os.environ.get('CLOUD_SQL_CONNECTION_NAME') + +# When deployed to App Engine, the `GAE_ENV` environment variable will be +# set to `standard` +if os.environ.get('GAE_ENV'): + # If deployed, use the local socket interface for accessing Cloud SQL + host = '/cloudsql/{}'.format(db_connection_name) +else: + # If running locally, use the TCP connections instead + # Set up Cloud SQL Proxy (cloud.google.com/sql/docs/mysql/sql-proxy) + # so that your application can use 127.0.0.1:3306 to connect to your + # Cloud SQL instance + host = '127.0.0.1' + +db_config = { + 'user': db_user, + 'password': db_password, + 'database': db_name, + 'host': host +} + +cnxpool = psycopg2.pool.ThreadedConnectionPool(minconn=1, maxconn=3, + **db_config) + +app = Flask(__name__) + + +@app.route('/') +def main(): + cnx = cnxpool.getconn() + with cnx.cursor() as cursor: + cursor.execute('SELECT NOW() as now;') + result = cursor.fetchall() + current_time = result[0][0] + cnx.commit() + cnxpool.putconn(cnx) + + return str(current_time) +# [END gae_python37_cloudsql_psql_pooling] + + +if __name__ == '__main__': + app.run(host='127.0.0.1', port=8080, debug=True) diff --git a/appengine/standard_python37/cloudsql/main_test.py b/appengine/standard_python37/cloudsql/main_test.py new file mode 100644 index 000000000000..e9496692e6a8 --- /dev/null +++ b/appengine/standard_python37/cloudsql/main_test.py @@ -0,0 +1,78 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest.mock import MagicMock + +import psycopg2.pool +import sqlalchemy + + +def test_main(): + import main_mysql + main_mysql.pymysql = MagicMock() + fetchall_mock = main_mysql.pymysql.connect().cursor().__enter__().fetchall + fetchall_mock.return_value = [['0']] + + main_mysql.app.testing = True + client = main_mysql.app.test_client() + + r = client.get('/') + assert r.status_code == 200 + assert '0' in r.data.decode('utf-8') + + +def test_main_pooling(): + sqlalchemy.create_engine = MagicMock() + + import main_mysql_pooling + + cnx_mock = main_mysql_pooling.sqlalchemy.create_engine().connect() + cnx_mock.execute().fetchall.return_value = [['0']] + + main_mysql_pooling.app.testing = True + client = main_mysql_pooling.app.test_client() + + r = client.get('/') + assert r.status_code == 200 + assert '0' in r.data.decode('utf-8') + + +def test_main_postgressql(): + import main_postgres + main_postgres.psycopg2.connect = MagicMock() + mock_cursor = main_postgres.psycopg2.connect().cursor() + mock_cursor.__enter__().fetchall.return_value = [['0']] + + main_postgres.app.testing = True + client = main_postgres.app.test_client() + + r = client.get('/') + assert r.status_code == 200 + assert '0' in r.data.decode('utf-8') + + +def test_main_postgressql_pooling(): + psycopg2.pool.ThreadedConnectionPool = MagicMock() + + import main_postgres_pooling + + mock_pool = main_postgres_pooling.psycopg2.pool.ThreadedConnectionPool() + mock_pool.getconn().cursor().__enter__().fetchall.return_value = [['0']] + + main_postgres_pooling.app.testing = True + client = main_postgres_pooling.app.test_client() + + r = client.get('/') + assert r.status_code == 200 + assert '0' in r.data.decode('utf-8') diff --git a/appengine/standard_python37/cloudsql/requirements.txt b/appengine/standard_python37/cloudsql/requirements.txt new file mode 100644 index 000000000000..262887104018 --- /dev/null +++ b/appengine/standard_python37/cloudsql/requirements.txt @@ -0,0 +1,4 @@ +psycopg2==2.7.5 +psycopg2-binary==2.7.5 +PyMySQL==0.9.2 +SQLAlchemy==1.2.12 \ No newline at end of file