Skip to content

Commit 22bff6b

Browse files
author
Jim Fulton
committed
Set up snippets
1 parent 080eda1 commit 22bff6b

File tree

9 files changed

+435
-1
lines changed

9 files changed

+435
-1
lines changed

owlbot.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import synthtool as s
2020
from synthtool import gcp
21-
21+
from synthtool.languages import python
2222

2323
REPO_ROOT = pathlib.Path(__file__).parent.absolute()
2424

@@ -201,6 +201,12 @@ def compliance(session):
201201
"""
202202
)
203203

204+
# ----------------------------------------------------------------------------
205+
# Samples templates
206+
# ----------------------------------------------------------------------------
207+
208+
python.py_samples(skip_readmes=True)
209+
204210
# ----------------------------------------------------------------------------
205211
# Final cleanup
206212
# ----------------------------------------------------------------------------

samples/__init__.py

Whitespace-only changes.

samples/pytest.ini

Whitespace-only changes.

samples/snippets/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright (c) 2021 The PyBigQuery Authors
2+
#
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
4+
# this software and associated documentation files (the "Software"), to deal in
5+
# the Software without restriction, including without limitation the rights to
6+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7+
# the Software, and to permit persons to whom the Software is furnished to do so,
8+
# subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in all
11+
# copies or substantial portions of the Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

samples/snippets/noxfile.py

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
# Copyright 2021 Google LLC
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 __future__ import print_function
16+
17+
import os
18+
from pathlib import Path
19+
import sys
20+
from typing import Callable, Dict, List, Optional
21+
22+
import nox
23+
24+
25+
BLACK_VERSION = "black==19.10b0"
26+
27+
# Copy `noxfile_config.py` to your directory and modify it instead.
28+
29+
# `TEST_CONFIG` dict is a configuration hook that allows users to
30+
# modify the test configurations. The values here should be in sync
31+
# with `noxfile_config.py`. Users will copy `noxfile_config.py` into
32+
# their directory and modify it.
33+
34+
TEST_CONFIG = {
35+
"ignored_versions": ["2.7"],
36+
# Old samples are opted out of enforcing Python type hints
37+
# All new samples should feature them
38+
"enforce_type_hints": False,
39+
# An envvar key for determining the project id to use. Change it
40+
# to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a
41+
# build specific Cloud project. You can also use your own string
42+
# to use your own Cloud project.
43+
"gcloud_project_env": "GOOGLE_CLOUD_PROJECT",
44+
# 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT',
45+
# If you need to use a specific version of pip,
46+
# change pip_version_override to the string representation
47+
# of the version number, for example, "20.2.4"
48+
"pip_version_override": None,
49+
# A dictionary you want to inject into your test. Don't put any
50+
# secrets here. These values will override predefined values.
51+
"envs": {},
52+
}
53+
54+
55+
try:
56+
# Ensure we can import noxfile_config in the project's directory.
57+
sys.path.append(".")
58+
from noxfile_config import TEST_CONFIG_OVERRIDE
59+
except ImportError as e:
60+
print("No user noxfile_config found: detail: {}".format(e))
61+
TEST_CONFIG_OVERRIDE = {}
62+
63+
# Update the TEST_CONFIG with the user supplied values.
64+
TEST_CONFIG.update(TEST_CONFIG_OVERRIDE)
65+
66+
67+
def get_pytest_env_vars() -> Dict[str, str]:
68+
"""Returns a dict for pytest invocation."""
69+
ret = {}
70+
71+
# Override the GCLOUD_PROJECT and the alias.
72+
env_key = TEST_CONFIG["gcloud_project_env"]
73+
# This should error out if not set.
74+
ret["GOOGLE_CLOUD_PROJECT"] = os.environ[env_key]
75+
76+
# Apply user supplied envs.
77+
ret.update(TEST_CONFIG["envs"])
78+
return ret
79+
80+
81+
# DO NOT EDIT - automatically generated.
82+
# All versions used to tested samples.
83+
ALL_VERSIONS = ["2.7", "3.6", "3.7", "3.8", "3.9"]
84+
85+
# Any default versions that should be ignored.
86+
IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"]
87+
88+
TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS])
89+
90+
INSTALL_LIBRARY_FROM_SOURCE = bool(os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False))
91+
#
92+
# Style Checks
93+
#
94+
95+
96+
def _determine_local_import_names(start_dir: str) -> List[str]:
97+
"""Determines all import names that should be considered "local".
98+
99+
This is used when running the linter to insure that import order is
100+
properly checked.
101+
"""
102+
file_ext_pairs = [os.path.splitext(path) for path in os.listdir(start_dir)]
103+
return [
104+
basename
105+
for basename, extension in file_ext_pairs
106+
if extension == ".py"
107+
or os.path.isdir(os.path.join(start_dir, basename))
108+
and basename not in ("__pycache__")
109+
]
110+
111+
112+
# Linting with flake8.
113+
#
114+
# We ignore the following rules:
115+
# E203: whitespace before ‘:’
116+
# E266: too many leading ‘#’ for block comment
117+
# E501: line too long
118+
# I202: Additional newline in a section of imports
119+
#
120+
# We also need to specify the rules which are ignored by default:
121+
# ['E226', 'W504', 'E126', 'E123', 'W503', 'E24', 'E704', 'E121']
122+
FLAKE8_COMMON_ARGS = [
123+
"--show-source",
124+
"--builtin=gettext",
125+
"--max-complexity=20",
126+
"--import-order-style=google",
127+
"--exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py",
128+
"--ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202",
129+
"--max-line-length=88",
130+
]
131+
132+
133+
@nox.session
134+
def lint(session: nox.sessions.Session) -> None:
135+
if not TEST_CONFIG["enforce_type_hints"]:
136+
session.install("flake8", "flake8-import-order")
137+
else:
138+
session.install("flake8", "flake8-import-order", "flake8-annotations")
139+
140+
local_names = _determine_local_import_names(".")
141+
args = FLAKE8_COMMON_ARGS + [
142+
"--application-import-names",
143+
",".join(local_names),
144+
".",
145+
]
146+
session.run("flake8", *args)
147+
148+
149+
#
150+
# Black
151+
#
152+
153+
154+
@nox.session
155+
def blacken(session: nox.sessions.Session) -> None:
156+
session.install(BLACK_VERSION)
157+
python_files = [path for path in os.listdir(".") if path.endswith(".py")]
158+
159+
session.run("black", *python_files)
160+
161+
162+
#
163+
# Sample Tests
164+
#
165+
166+
167+
PYTEST_COMMON_ARGS = ["--junitxml=sponge_log.xml"]
168+
169+
170+
def _session_tests(
171+
session: nox.sessions.Session, post_install: Callable = None
172+
) -> None:
173+
if TEST_CONFIG["pip_version_override"]:
174+
pip_version = TEST_CONFIG["pip_version_override"]
175+
session.install(f"pip=={pip_version}")
176+
"""Runs py.test for a particular project."""
177+
if os.path.exists("requirements.txt"):
178+
if os.path.exists("constraints.txt"):
179+
session.install("-r", "requirements.txt", "-c", "constraints.txt")
180+
else:
181+
session.install("-r", "requirements.txt")
182+
183+
if os.path.exists("requirements-test.txt"):
184+
if os.path.exists("constraints-test.txt"):
185+
session.install("-r", "requirements-test.txt", "-c", "constraints-test.txt")
186+
else:
187+
session.install("-r", "requirements-test.txt")
188+
189+
if INSTALL_LIBRARY_FROM_SOURCE:
190+
session.install("-e", _get_repo_root())
191+
192+
if post_install:
193+
post_install(session)
194+
195+
session.run(
196+
"pytest",
197+
*(PYTEST_COMMON_ARGS + session.posargs),
198+
# Pytest will return 5 when no tests are collected. This can happen
199+
# on travis where slow and flaky tests are excluded.
200+
# See http://doc.pytest.org/en/latest/_modules/_pytest/main.html
201+
success_codes=[0, 5],
202+
env=get_pytest_env_vars(),
203+
)
204+
205+
206+
@nox.session(python=ALL_VERSIONS)
207+
def py(session: nox.sessions.Session) -> None:
208+
"""Runs py.test for a sample using the specified version of Python."""
209+
if session.python in TESTED_VERSIONS:
210+
_session_tests(session)
211+
else:
212+
session.skip(
213+
"SKIPPED: {} tests are disabled for this sample.".format(session.python)
214+
)
215+
216+
217+
#
218+
# Readmegen
219+
#
220+
221+
222+
def _get_repo_root() -> Optional[str]:
223+
""" Returns the root folder of the project. """
224+
# Get root of this repository. Assume we don't have directories nested deeper than 10 items.
225+
p = Path(os.getcwd())
226+
for i in range(10):
227+
if p is None:
228+
break
229+
if Path(p / ".git").exists():
230+
return str(p)
231+
# .git is not available in repos cloned via Cloud Build
232+
# setup.py is always in the library's root, so use that instead
233+
# https://github.com/googleapis/synthtool/issues/792
234+
if Path(p / "setup.py").exists():
235+
return str(p)
236+
p = p.parent
237+
raise Exception("Unable to detect repository root.")
238+
239+
240+
GENERATED_READMES = sorted([x for x in Path(".").rglob("*.rst.in")])
241+
242+
243+
@nox.session
244+
@nox.parametrize("path", GENERATED_READMES)
245+
def readmegen(session: nox.sessions.Session, path: str) -> None:
246+
"""(Re-)generates the readme for a sample."""
247+
session.install("jinja2", "pyyaml")
248+
dir_ = os.path.dirname(path)
249+
250+
if os.path.exists(os.path.join(dir_, "requirements.txt")):
251+
session.install("-r", os.path.join(dir_, "requirements.txt"))
252+
253+
in_file = os.path.join(dir_, "README.rst.in")
254+
session.run(
255+
"python", _get_repo_root() + "/scripts/readme-gen/readme_gen.py", in_file
256+
)

samples/snippets/pandas_read_sql.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env python
2+
# Copyright (c) 2021 The PyBigQuery Authors
3+
#
4+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
5+
# this software and associated documentation files (the "Software"), to deal in
6+
# the Software without restriction, including without limitation the rights to
7+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8+
# the Software, and to permit persons to whom the Software is furnished to do so,
9+
# subject to the following conditions:
10+
#
11+
# The above copyright notice and this permission notice shall be included in all
12+
# copies or substantial portions of the Software.
13+
#
14+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
21+
22+
def read_data_into_pandas_using_read_sql() -> None:
23+
"""Show how to read BigQuery data using the pandas `read_sql` function.
24+
"""
25+
import pandas as pd
26+
from sqlalchemy import create_engine
27+
28+
# Use project from credentials provided by your environment.
29+
# https://googleapis.dev/python/google-api-core/latest/auth.html
30+
engine = create_engine("bigquery://")
31+
32+
# Read data from the austin 311-compliant table in the google
33+
# public datasets.
34+
35+
# The table is given in three parts in this example:
36+
# PROJECT.DATASET.TABLE
37+
38+
df = pd.read_sql(
39+
"""select created_date, complaint_description
40+
from bigquery-public-data.austin_311.311_service_requests
41+
limit 10
42+
""",
43+
engine,
44+
)
45+
46+
print(df)
47+
48+
49+
if __name__ == "__main__":
50+
read_data_into_pandas_using_read_sql()
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright (c) 2021 The PyBigQuery Authors
2+
#
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
4+
# this software and associated documentation files (the "Software"), to deal in
5+
# the Software without restriction, including without limitation the rights to
6+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7+
# the Software, and to permit persons to whom the Software is furnished to do so,
8+
# subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in all
11+
# copies or substantial portions of the Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19+
20+
from . import pandas_read_sql
21+
22+
23+
def test_pandas_read_sql(capsys) -> None:
24+
pandas_read_sql.read_data_into_pandas_using_read_sql()
25+
26+
out, err = capsys.readouterr()
27+
28+
# Check header for expected collumns
29+
out = out.strip().split("\n")
30+
assert len(out) == 11
31+
assert out[0].strip().split() == ["created_date", "complaint_description"]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
attrs==21.2.0
2+
importlib-metadata==4.6.1
3+
iniconfig==1.1.1
4+
packaging==21.0
5+
pluggy==0.13.1
6+
py==1.10.0
7+
pyparsing==2.4.7
8+
pytest==6.2.4
9+
toml==0.10.2
10+
typing-extensions==3.10.0.0
11+
zipp==3.5.0

0 commit comments

Comments
 (0)