Skip to content

Commit b54306a

Browse files
committed
Updated admin nox configuration and contributors guide
1. Copied https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile-template.py to noxfile.py. 2. Modified noxfile_config.py to allow env variable overrides. 3. Updated the contributors guide to include sections on style and tests.
1 parent 3bfc087 commit b54306a

3 files changed

Lines changed: 147 additions & 66 deletions

File tree

CONTRIBUTING.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,66 @@ information on using pull requests.
2727
This project follows [Google's Open Source Community
2828
Guidelines](https://opensource.google.com/conduct/).
2929

30+
## Code Style
31+
32+
All Python samples should follow the best practices defined in the [PEP 8 style
33+
guide](https://www.python.org/dev/peps/pep-0008/) and the [Google Python Style
34+
Guide](http://google.github.io/styleguide/pyguide.html). The automated linting
35+
process for Python samples uses [flake8](http://flake8.pycqa.org/en/latest/) to
36+
verify conformance to common Python coding standards, so the use of flake8 is
37+
recommended.
38+
39+
If you prefer to use [pylint](https://www.pylint.org/), note that Python samples
40+
for this repo are not required to conform to pylint’s default settings outside
41+
the scope of PEP 8, such as the “too many arguments” or “too many local
42+
variables” warnings.
43+
44+
The use of [Black](https://pypi.org/project/black/) to standardize code
45+
formatting and simplify diffs is recommended.
46+
47+
The default noxfile defines a `blacken` session for convenience. If you have
48+
pyenv configured, you can check and format all files in `google-analytics-admin`
49+
or `google-analytics-data` using the following command:
50+
51+
```sh
52+
nox -s lint blacken
53+
```
54+
55+
## Running the tests
56+
57+
1. Configure your environment and credentials as described in the
58+
[README](README.md).
59+
2. Determine which GA4 property you want to use for tests, and note its
60+
property ID.
61+
3. Change into the directory of the project you want to test (either
62+
`google-analytics-admin` or `google-analytics-data`).
63+
4. Configure environment variables required by tests using the following
64+
commands:
65+
66+
```sh
67+
# These values are required by Admin and Data API samples tests.
68+
export GOOGLE_CLOUD_PROJECT=
69+
export GA_TEST_PROPERTY_ID=
70+
# These values are only required by Admin API samples tests.
71+
export GA_TEST_ACCOUNT_ID=
72+
export GA_TEST_WEB_DATA_STREAM_ID=
73+
export GA_TEST_WEB_DATA_SECRET_ID=
74+
export GA_TEST_CONVERSION_EVENT_ID=
75+
```
76+
77+
5. Execute tests from the `google-analytics-admin` or `google-analytics-data`
78+
directory, using either `nox` or `pytest`. Using `nox` provides the
79+
additional benefit of formatting all files, and also automatically creates a
80+
virtual environment to test samples using different versions of Python.
81+
82+
* Using `nox`:
83+
84+
```sh
85+
nox
86+
```
87+
88+
* Using `pytest`:
89+
90+
```sh
91+
pytest
92+
```

google-analytics-admin/noxfile.py

Lines changed: 68 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,35 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from __future__ import annotations
1516
from __future__ import print_function
1617

18+
from collections.abc import Callable
1719
import glob
1820
import os
1921
from pathlib import Path
2022
import sys
21-
from typing import Callable, Dict, Optional
2223

2324
import nox
2425

26+
2527
# WARNING - WARNING - WARNING - WARNING - WARNING
2628
# WARNING - WARNING - WARNING - WARNING - WARNING
27-
# DO NOT EDIT THIS FILE EVER!
29+
# BE CAREFUL WHEN EDITING THIS FILE!
2830
# WARNING - WARNING - WARNING - WARNING - WARNING
2931
# WARNING - WARNING - WARNING - WARNING - WARNING
3032

31-
BLACK_VERSION = "black==22.3.0"
32-
ISORT_VERSION = "isort==5.10.1"
33-
3433
# Copy `noxfile_config.py` to your directory and modify it instead.
3534

35+
3636
# `TEST_CONFIG` dict is a configuration hook that allows users to
3737
# modify the test configurations. The values here should be in sync
3838
# with `noxfile_config.py`. Users will copy `noxfile_config.py` into
3939
# their directory and modify it.
4040

4141
TEST_CONFIG = {
4242
# You can opt out from the test for specific Python versions.
43-
"ignored_versions": [],
43+
"ignored_versions": ["2.7", "3.7", "3.9", "3.10", "3.11"],
4444
# Old samples are opted out of enforcing Python type hints
4545
# All new samples should feature them
4646
"enforce_type_hints": False,
@@ -72,33 +72,30 @@
7272
TEST_CONFIG.update(TEST_CONFIG_OVERRIDE)
7373

7474

75-
def get_pytest_env_vars() -> Dict[str, str]:
75+
def get_pytest_env_vars() -> dict[str, str]:
7676
"""Returns a dict for pytest invocation."""
7777
ret = {}
7878

7979
# Override the GCLOUD_PROJECT and the alias.
8080
env_key = TEST_CONFIG["gcloud_project_env"]
8181
# This should error out if not set.
8282
ret["GOOGLE_CLOUD_PROJECT"] = os.environ[env_key]
83+
ret["GCLOUD_PROJECT"] = os.environ[env_key] # deprecated
8384

8485
# Apply user supplied envs.
8586
ret.update(TEST_CONFIG["envs"])
8687
return ret
8788

8889

89-
# DO NOT EDIT - automatically generated.
90-
# All versions used to test samples.
91-
ALL_VERSIONS = ["3.7", "3.8", "3.9", "3.10", "3.11"]
90+
# All versions used to tested samples.
91+
ALL_VERSIONS = ["2.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
9292

9393
# Any default versions that should be ignored.
9494
IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"]
9595

9696
TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS])
9797

98-
INSTALL_LIBRARY_FROM_SOURCE = os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False) in (
99-
"True",
100-
"true",
101-
)
98+
INSTALL_LIBRARY_FROM_SOURCE = bool(os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False))
10299

103100
# Error if a python version is missing
104101
nox.options.error_on_missing_interpreters = True
@@ -108,9 +105,26 @@ def get_pytest_env_vars() -> Dict[str, str]:
108105
#
109106

110107

108+
def _determine_local_import_names(start_dir: str) -> list[str]:
109+
"""Determines all import names that should be considered "local".
110+
111+
This is used when running the linter to ensure that import order is
112+
properly checked.
113+
"""
114+
file_ext_pairs = [os.path.splitext(path) for path in os.listdir(start_dir)]
115+
return [
116+
basename
117+
for basename, extension in file_ext_pairs
118+
if extension == ".py"
119+
or os.path.isdir(os.path.join(start_dir, basename))
120+
and basename not in ("__pycache__")
121+
]
122+
123+
111124
# Linting with flake8.
112125
#
113126
# We ignore the following rules:
127+
# ANN101: missing type annotation for self in method
114128
# E203: whitespace before ‘:’
115129
# E266: too many leading ‘#’ for block comment
116130
# E501: line too long
@@ -122,20 +136,24 @@ def get_pytest_env_vars() -> Dict[str, str]:
122136
"--show-source",
123137
"--builtin=gettext",
124138
"--max-complexity=20",
139+
"--import-order-style=google",
125140
"--exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py",
126-
"--ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202",
141+
"--ignore=ANN101,E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202",
127142
"--max-line-length=88",
128143
]
129144

130145

131146
@nox.session
132147
def lint(session: nox.sessions.Session) -> None:
133148
if not TEST_CONFIG["enforce_type_hints"]:
134-
session.install("flake8")
149+
session.install("flake8", "flake8-import-order")
135150
else:
136-
session.install("flake8", "flake8-annotations")
151+
session.install("flake8", "flake8-import-order", "flake8-annotations")
137152

153+
local_names = _determine_local_import_names(".")
138154
args = FLAKE8_COMMON_ARGS + [
155+
"--application-import-names",
156+
",".join(local_names),
139157
".",
140158
]
141159
session.run("flake8", *args)
@@ -148,30 +166,9 @@ def lint(session: nox.sessions.Session) -> None:
148166

149167
@nox.session
150168
def blacken(session: nox.sessions.Session) -> None:
151-
"""Run black. Format code to uniform standard."""
152-
session.install(BLACK_VERSION)
153-
python_files = [path for path in os.listdir(".") if path.endswith(".py")]
154-
155-
session.run("black", *python_files)
156-
157-
158-
#
159-
# format = isort + black
160-
#
161-
162-
163-
@nox.session
164-
def format(session: nox.sessions.Session) -> None:
165-
"""
166-
Run isort to sort imports. Then run black
167-
to format code to uniform standard.
168-
"""
169-
session.install(BLACK_VERSION, ISORT_VERSION)
169+
session.install("black")
170170
python_files = [path for path in os.listdir(".") if path.endswith(".py")]
171171

172-
# Use the --fss option to sort imports using strict alphabetical order.
173-
# See https://pycqa.github.io/isort/docs/configuration/options.html#force-sort-within-sections
174-
session.run("isort", "--fss", *python_files)
175172
session.run("black", *python_files)
176173

177174

@@ -187,10 +184,8 @@ def _session_tests(
187184
session: nox.sessions.Session, post_install: Callable = None
188185
) -> None:
189186
# check for presence of tests
190-
test_list = glob.glob("**/*_test.py", recursive=True) + glob.glob(
191-
"**/test_*.py", recursive=True
192-
)
193-
test_list.extend(glob.glob("**/tests", recursive=True))
187+
test_list = glob.glob("*_test.py") + glob.glob("test_*.py")
188+
test_list.extend(glob.glob("tests"))
194189

195190
if len(test_list) == 0:
196191
print("No tests found, skipping directory.")
@@ -199,23 +194,42 @@ def _session_tests(
199194
if TEST_CONFIG["pip_version_override"]:
200195
pip_version = TEST_CONFIG["pip_version_override"]
201196
session.install(f"pip=={pip_version}")
197+
else:
198+
session.install("--upgrade", "pip")
199+
202200
"""Runs py.test for a particular project."""
203201
concurrent_args = []
204202
if os.path.exists("requirements.txt"):
205-
if os.path.exists("constraints.txt"):
206-
session.install("-r", "requirements.txt", "-c", "constraints.txt")
207-
else:
208-
session.install("-r", "requirements.txt")
209203
with open("requirements.txt") as rfile:
210204
packages = rfile.read()
205+
if os.path.exists("constraints.txt"):
206+
session.install(
207+
"-r",
208+
"requirements.txt",
209+
"-c",
210+
"constraints.txt",
211+
"--only-binary",
212+
":all",
213+
)
214+
elif "pyspark" in packages:
215+
session.install("-r", "requirements.txt", "--use-pep517")
216+
else:
217+
session.install("-r", "requirements.txt", "--only-binary", ":all")
211218

212219
if os.path.exists("requirements-test.txt"):
213-
if os.path.exists("constraints-test.txt"):
214-
session.install("-r", "requirements-test.txt", "-c", "constraints-test.txt")
215-
else:
216-
session.install("-r", "requirements-test.txt")
217220
with open("requirements-test.txt") as rtfile:
218221
packages += rtfile.read()
222+
if os.path.exists("constraints-test.txt"):
223+
session.install(
224+
"-r",
225+
"requirements-test.txt",
226+
"-c",
227+
"constraints-test.txt",
228+
"--only-binary",
229+
":all",
230+
)
231+
else:
232+
session.install("-r", "requirements-test.txt", "--only-binary", ":all")
219233

220234
if INSTALL_LIBRARY_FROM_SOURCE:
221235
session.install("-e", _get_repo_root())
@@ -255,20 +269,16 @@ def py(session: nox.sessions.Session) -> None:
255269
#
256270

257271

258-
def _get_repo_root() -> Optional[str]:
272+
def _get_repo_root() -> str | None:
259273
"""Returns the root folder of the project."""
260-
# Get root of this repository. Assume we don't have directories nested deeper than 10 items.
274+
# Get root of this repository.
275+
# Assume we don't have directories nested deeper than 10 items.
261276
p = Path(os.getcwd())
262277
for i in range(10):
263278
if p is None:
264279
break
265280
if Path(p / ".git").exists():
266281
return str(p)
267-
# .git is not available in repos cloned via Cloud Build
268-
# setup.py is always in the library's root, so use that instead
269-
# https://github.com/googleapis/synthtool/issues/792
270-
if Path(p / "setup.py").exists():
271-
return str(p)
272282
p = p.parent
273283
raise Exception("Unable to detect repository root.")
274284

google-analytics-admin/noxfile_config.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,30 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import os
16+
1517
TEST_CONFIG_OVERRIDE = {
18+
# You can opt out from the test for specific Python versions.
19+
"ignored_versions": ["2.7"],
1620
# An envvar key for determining the project id to use. Change it
1721
# to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a
1822
# build specific Cloud project. You can also use your own string
1923
# to use your own Cloud project.
20-
"gcloud_project_env": "BUILD_SPECIFIC_GCLOUD_PROJECT",
24+
"gcloud_project_env": "GOOGLE_CLOUD_PROJECT",
2125
# 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT',
2226
# A dictionary you want to inject into your test. Don't put any
2327
# secrets here. These values will override predefined values.
2428
"envs": {
25-
"GA_TEST_PROPERTY_ID": "276206997",
26-
"GA_TEST_ACCOUNT_ID": "199820965",
27-
"GA_TEST_USER_LINK_ID": "103401743041912607932",
28-
"GA_TEST_PROPERTY_USER_LINK_ID": "105231969274497648555",
29-
"GA_TEST_WEB_DATA_STREAM_ID": "2828068992",
30-
"GA_TEST_WEB_DATA_SECRET_ID": "2994983412",
31-
"GA_TEST_CONVERSION_EVENT_ID": "2719963095",
29+
"GA_TEST_PROPERTY_ID": os.getenv("GA_TEST_PROPERTY_ID", "276206997"),
30+
"GA_TEST_ACCOUNT_ID": os.getenv("GA_TEST_ACCOUNT_ID", "199820965"),
31+
"GA_TEST_WEB_DATA_STREAM_ID": os.getenv(
32+
"GA_TEST_WEB_DATA_STREAM_ID", "2828068992"
33+
),
34+
"GA_TEST_WEB_DATA_SECRET_ID": os.getenv(
35+
"GA_TEST_WEB_DATA_SECRET_ID", "2994983412"
36+
),
37+
"GA_TEST_CONVERSION_EVENT_ID": os.getenv(
38+
"GA_TEST_CONVERSION_EVENT_ID", "2719963095"
39+
),
3240
},
3341
}

0 commit comments

Comments
 (0)