Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add autogenerated snippets #845

Merged
merged 26 commits into from
May 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
701476f
feat: change sampels to align with new design
busunkim96 Mar 15, 2021
c998993
feat: fulfill requirements for P0 snippetgen
busunkim96 Mar 16, 2021
6149983
fix: fix type issues
busunkim96 Mar 17, 2021
63a0509
fix: disable autogen snippets by default
busunkim96 Apr 6, 2021
793d108
fix: resolve mypy issues
busunkim96 Apr 6, 2021
be303df
fix: use trim_blocks and lstrip_blocks for jinja templates
busunkim96 Apr 8, 2021
c4f95cf
fix: fix more templates
busunkim96 Apr 13, 2021
1666d0a
fix: fix sample unit tests
busunkim96 Apr 13, 2021
23df2f3
test: add separate snippetgen session and basic golden snippet
busunkim96 Apr 19, 2021
a302db9
test: get existing unit tests to pass
busunkim96 Apr 20, 2021
093f931
chore: comment out manifest gen
busunkim96 Apr 20, 2021
2de6ac8
chore: tweak dummy ident in tests
busunkim96 Apr 20, 2021
ee1897c
feat: fill out basic snippets for other types
busunkim96 Apr 21, 2021
6b46aab
Merge branch 'master' into snippet-gen
busunkim96 Apr 22, 2021
0944178
fix: properly resolve merge conflict
busunkim96 Apr 22, 2021
a487f7d
chore: comment cleanup
busunkim96 Apr 23, 2021
351f0fe
Merge branch 'master' into snippet-gen
busunkim96 Apr 23, 2021
0ba949c
test: fix protoc installation, add snippetgen tests
busunkim96 Apr 23, 2021
0425783
Merge branch 'snippet-gen' of github.com:googleapis/gapic-generator-p…
busunkim96 Apr 23, 2021
769e06f
fix: fix style
busunkim96 Apr 23, 2021
e6224a6
chore: tell snippetgen to ignore this repo
busunkim96 Apr 23, 2021
0fce06c
fix: address review comments
busunkim96 May 3, 2021
3c3cb5a
fix: re-add templates on jinja files
busunkim96 May 3, 2021
b218789
chore: ignore test_output dir
busunkim96 May 3, 2021
cca0f54
test: add default host to gen sample spec test
busunkim96 May 3, 2021
ce309df
Merge branch 'master' into snippet-gen
software-dov May 3, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/snippet-bot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# https://github.com/googleapis/repo-automation-bots/tree/master/packages/snippet-bot
ignoreFiles:
- "**/*.py"
20 changes: 20 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,26 @@ jobs:
run: python -m pip install nox
- name: Typecheck the generated output.
run: nox -s showcase_mypy${{ matrix.variant }}
snippetgen:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install system dependencies.
run: |
sudo apt-get update
sudo apt-get install -y curl pandoc unzip gcc
- name: Install nox.
run: python -m pip install nox
- name: Check autogenerated snippets.
run: nox -s snippetgen
unit:
strategy:
matrix:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,7 @@ pylintrc.test

# pyenv
.python-version

# Test dependencies and output
api-common-protos
tests/snippetgen/.test_output
123 changes: 62 additions & 61 deletions gapic/generator/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@

import jinja2
import yaml
import itertools
import re
import os
import typing
from typing import Any, DefaultDict, Dict, Mapping
from hashlib import sha256
from collections import OrderedDict, defaultdict
Expand Down Expand Up @@ -107,12 +109,12 @@ def get_response(
template_name, api_schema=api_schema, opts=opts)
)

sample_output = self._generate_samples_and_manifest(
api_schema,
self._env.get_template(sample_templates[0]),
) if sample_templates else {}

output_files.update(sample_output)
if sample_templates:
sample_output = self._generate_samples_and_manifest(
api_schema, self._env.get_template(sample_templates[0]),
opts=opts,
)
output_files.update(sample_output)

# Return the CodeGeneratorResponse output.
res = CodeGeneratorResponse(
Expand All @@ -121,12 +123,13 @@ def get_response(
return res

def _generate_samples_and_manifest(
self, api_schema: api.API, sample_template: jinja2.Template,
) -> Dict[str, CodeGeneratorResponse.File]:
self, api_schema: api.API, sample_template: jinja2.Template, *, opts: Options) -> Dict:
"""Generate samples and samplegen manifest for the API.

Arguments:
api_schema (api.API): The schema for the API to which the samples belong.
sample_template (jinja2.Template): The template to use to generate samples.
opts (Options): Additional generator options.

Returns:
Dict[str, CodeGeneratorResponse.File]: A dict mapping filepath to rendered file.
Expand All @@ -137,56 +140,50 @@ def _generate_samples_and_manifest(
id_to_hash_to_spec: DefaultDict[str,
Dict[str, Any]] = defaultdict(dict)

STANDALONE_TYPE = "standalone"
for config_fpath in self._sample_configs:
with open(config_fpath) as f:
configs = yaml.safe_load_all(f.read())

spec_generator = (
spec
for cfg in configs
if is_valid_sample_cfg(cfg)
for spec in cfg.get("samples", [])
# If unspecified, assume a sample config describes a standalone.
# If sample_types are specified, standalone samples must be
# explicitly enabled.
if STANDALONE_TYPE in spec.get("sample_type", [STANDALONE_TYPE])
)
# Autogenerated sample specs
autogen_specs: typing.List[typing.Dict[str, Any]] = []
if opts.autogen_snippets:
autogen_specs = list(
samplegen.generate_sample_specs(api_schema, opts=opts))

# Also process any handwritten sample specs
handwritten_specs = samplegen.parse_handwritten_specs(
self._sample_configs)

sample_specs = autogen_specs + list(handwritten_specs)

for spec in sample_specs:
# Every sample requires an ID. This may be provided
# by a samplegen config author.
# If no ID is provided, fall back to the region tag.
#
# Ideally the sample author should pick a descriptive, unique ID,
# but this may be impractical and can be error-prone.
spec_hash = sha256(str(spec).encode("utf8")).hexdigest()[:8]
sample_id = spec.get("id") or spec.get("region_tag") or spec_hash
spec["id"] = sample_id

for spec in spec_generator:
# Every sample requires an ID, preferably provided by the
# samplegen config author.
# If no ID is provided, fall back to the region tag.
# If there's no region tag, generate a unique ID.
#
# Ideally the sample author should pick a descriptive, unique ID,
# but this may be impractical and can be error-prone.
spec_hash = sha256(str(spec).encode("utf8")).hexdigest()[:8]
sample_id = spec.get("id") or spec.get(
"region_tag") or spec_hash
spec["id"] = sample_id

hash_to_spec = id_to_hash_to_spec[sample_id]
if spec_hash in hash_to_spec:
raise DuplicateSample(
f"Duplicate samplegen spec found: {spec}")

hash_to_spec[spec_hash] = spec

out_dir = "samples"
hash_to_spec = id_to_hash_to_spec[sample_id]

if spec_hash in hash_to_spec:
raise DuplicateSample(
f"Duplicate samplegen spec found: {spec}")

hash_to_spec[spec_hash] = spec

out_dir = "samples/generated_samples"
fpath_to_spec_and_rendered = {}
for hash_to_spec in id_to_hash_to_spec.values():
for spec_hash, spec in hash_to_spec.items():
id_is_unique = len(hash_to_spec) == 1
# The ID is used to generate the file name and by sample tester
# to link filenames to invoked samples. It must be globally unique.
# The ID is used to generate the file name. It must be globally unique.
if not id_is_unique:
spec["id"] += f"_{spec_hash}"

sample = samplegen.generate_sample(
spec, api_schema, sample_template,)

fpath = spec["id"] + ".py"
fpath = utils.to_snake_case(spec["id"]) + ".py"
fpath_to_spec_and_rendered[os.path.join(out_dir, fpath)] = (
spec,
sample,
Expand All @@ -199,20 +196,24 @@ def _generate_samples_and_manifest(
for fname, (_, sample) in fpath_to_spec_and_rendered.items()
}

# Only generate a manifest if we generated samples.
if output_files:
manifest_fname, manifest_doc = manifest.generate(
(
(fname, spec)
for fname, (spec, _) in fpath_to_spec_and_rendered.items()
),
api_schema,
)

manifest_fname = os.path.join(out_dir, manifest_fname)
output_files[manifest_fname] = CodeGeneratorResponse.File(
content=manifest_doc.render(), name=manifest_fname
)
# TODO(busunkim): Re-enable manifest generation once metadata
# format has been formalized.
# https://docs.google.com/document/d/1ghBam8vMj3xdoe4xfXhzVcOAIwrkbTpkMLgKc9RPD9k/edit#heading=h.sakzausv6hue
#
# if output_files:

# manifest_fname, manifest_doc = manifest.generate(
# (
# (fname, spec)
# for fname, (spec, _) in fpath_to_spec_and_rendered.items()
# ),
# api_schema,
# )

# manifest_fname = os.path.join(out_dir, manifest_fname)
# output_files[manifest_fname] = CodeGeneratorResponse.File(
# content=manifest_doc.render(), name=manifest_fname
# )

return output_files

Expand Down