Skip to content

[Syntax Review] Additional types of SampleGen code samples - #9970

Closed
beccasaurus wants to merge 5 commits into
googleapis:masterfrom
beccasaurus:samplegen-additional-sample-types-for-review
Closed

beccasaurus wants to merge 5 commits into
googleapis:masterfrom
beccasaurus:samplegen-additional-sample-types-for-review

Conversation

@beccasaurus

@beccasaurus beccasaurus commented Dec 12, 2019 •

Copy link
Copy Markdown
Contributor

Follow-up code review PR –

To evaluate the readiness of 🐍 Python's GAPIC sample generator for use on cloud.google.com,
this provides a full set of samples which demonstrate the basic features of generated samples.

The .yaml sample config files for these samples can all be found here: .yaml sample configs

The first review Pull Request contained samples to show the basic structure as well as LRO samples.

This contains samples demonstrating those features and a few more use-cases for review –

Sample Description
samplegen_basics.py Shows basic structure/anatomy of Python samples
samplegen_no_config.py Minimal sample example (zero configuration)
samplegen_no_response.py API method which returns Empty
samplegen_paged.py Paged API method (iterates over pages of results)
samplegen_resource_path.py Sample which requires user's Google Project ID
samplegen_map_field_access.py Access map value by key and enumerate keys/values
samplegen_read_and_write_files.py Read binary file into bytes & write string to local file
samplegen_repeated_fields.py Repeated request field & show loop/access by index
samplegen_write_bytes_to_file.py Write bytes field in response to local file
samplegen_lro.py Call long-running operation API endpoint

This Pull Request is focused on feedback collection & a similar PR is being sent to all languages.

Once you've completed your review and left comments, you may feel free to close the PR!

We will scope out making all of the requested changes and reach out to language team offline.

🙏 Thanks in advance for your review!


P.S. Nits welcome! We'd like to get these as polished as possible for usage on cloud.google.com.

Please feel free to note in comments whether something is: 🛑 Blocker -vs- 📌 Nit


🔍 Known Issues

  1. Should import libraries w/o version suffix, e.g. from google.cloud import speech_v1 as speech
  2. Docstring is missing : and is missing type, it should be var_name (Type): description here
  3. ❓ Open Question: Is the preference to show the method definition or only show method body?

📑 (internal)

@googlebot googlebot added the cla: yes This human has signed the Contributor License Agreement. label Dec 12, 2019
@beccasaurus

Copy link
Copy Markdown
Contributor Author

@busunkim96 Follow-up PR, as promised!

This shows additional syntax which previous PRs didn't include, so y'all can see all of the main snippets used to generate these Python samples. Please feel free to leave nits and blockers alike!

I mentioned some carry-over comments from the previous PR in the description above.

# description: This is the sample description
# usage: python3 samples/v1/samplegen_basics.py [--display_name "This is the default value of the display_name request field"]

# [START samplegen_basics]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tswast You mentioned a preference for not showing the method definition in the embedded code? (START/END)

Do you know if that's the standard nowadays? I've seen both types (with and without method signature).

No opinion on my end! The template for samples can be updated to whatever is standard! 😄

Does anyone have a canonical example? e.g. for Node.js they have these canonical examples

I'd love to update the structure if that's what's needed. Would it be a blocker if we show the method definition?

/cc @busunkim96 – This is the biggest piece of feedback from the previous PR ^

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python never settled on the canonical format, but there are a few of us working towards doing so (the original PR was currently here, and we have an AI on cleaning it up and creating a sample style guide).

Personally, I have a slight preference to leaving the function signature in (since it would make it easier for me to copy/paste into existing code).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for that context @kurtisvg!

I'm not biased in either direction, iirc we didn't originally have the method signature and then we added it to the generator template.

My preference: "leave the function signature in" +1

Happy to change this at any time!

I would find it most helpful if we can say that it's OK to launch in Q1 with the method signature and we can change after (ie. let's not block on this one item)

We should block on things like fixing the docstring format, though. Which we'll fix!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 - this is a nit for me.

@gguuss gguuss left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few nits :)

language = "en"

# The text content to analyze
content = "Googleplex is at 1600 Amphitheatre Parkway, Mountain View, CA."

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as outdated.

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we've settled on blacken, I think the double quotes have to stay.

I'm gonna resolve (hide) this thread. Adding a new comment will re-open, so feel free to follow-up with additional/different thoughts!


# [START samplegen_read_and_write_files]
from google.cloud import speech_v1p1beta1
from google.cloud.speech_v1p1beta1 import enums

@gguuss gguuss Dec 12, 2019 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer importing modules over members. Maybe just have the following

from google.cloud import speech_v1p1beta1

And later using speech_v1p1beta1.enums... instead of aliasing enums globally.

This helps to avoid alias collisions should people be importing multiple client libraries.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack. Thanks for pointing this out!

If I remember correctly, some LOC can be very long with <module>.<enums>. for each enum. If the generator were smart enough, it could decide when [not] to import enums.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gguuss @leahecole et al

Does this fall into nit or blocker category?

And are we sure that this change should be made?

I've seen a preference for importing enums – it's a style I've seen in a bunch of code samples. In python-docs-samples, you can find the import enums style used for all of these 10 APIs:

  • Asset
  • BigTable
  • KMS
  • Natural Language
  • Speech-to-Text
  • AutoML Tables
  • Text-to-Speech
  • Translation
  • Video Intelligence
  • Vision

Search for "import enums"

# [END samplegen_read_and_write_files]


def main():

This comment was marked as outdated.

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hiding this thread in favor of this one above (duplicate)

@busunkim96

Copy link
Copy Markdown
Contributor

@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ Note: this "no config" sample is different from the rest. Rather than demonstrating a feature, it shows what a sample looks like when not configured at all.

These samples could be generated for every API method without any manual configuration.

Review of the "no config" sample is not required, this is more of an FYI :)

@kurtisvg kurtisvg left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another topic of discussion (if it hasn't already been brought up) is that it would be helpful if Python3 style type hints were included (especially for client responses)

@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this will fix itself with the transition to the µgenerator, I'm hiding this thread.

It's a good catch! But it comes from the common template which the monolithic generator uses for all Python files, not just code samples.


client = language_v1.LanguageServiceClient()

type_ = enums.Document.Type.PLAIN_TEXT

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with manually adjusting for now - presumable these auto-gen samples will still get reviewed like normal ones, so we can encourage it that way if it's listed in the sample guidelines.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@busunkim96 Any idea if the Python documentation includes the available enum values with descriptions?

This is googleapis/artman#579 / #7503

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder... since the generators have the .proto, maybe including the list in the samples is the right way to go. We automatically add a comment which lists the available options. No URL required (but great if possible to construct).

@software-dov Thoughts on grabbing the enum display names in the sample generator and automatically including them?

This is language agnostic but seems to be one of the most popular pieces of feedback! 🎉

response = client.analyze_entities(document)
for entity in response.entities:
# Access value by key:
print(u"URL: {}".format(entity.metadata["wikipedia_url"]))

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☹️

How about we do this?

  1. Remove u""
  2. Don't use f"" (yet)

print(u"{}: {}".format(key, value))

# Loop over just keys:
for the_key, _ in entity.metadata.items():

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

print(u"Key: {}".format(the_key))

# Loop over just values:
for _, the_value in entity.metadata.items():

This comment was marked as off-topic.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or better yet,

Suggested change
for _, the_value in entity.metadata.items():
for value in entity.metadata.values():

@beccasaurus beccasaurus Dec 18, 2019 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GOOD CATCH 🎣

Will update to .values()

# A list of strings containing words and phrases "hints"
phrases_element = "Fox in socks"
phrases_element_2 = "Knox in box"
phrases = [phrases_element, phrases_element_2]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can we just inline these phrase_elements? They only appear to be used in the array, so attaching a variable name seems unnecessary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad you brought this up, I'd be surprised if you hadn't. These variables are pretty gross 😔

speech_contexts_element = {"phrases": phrases}
speech_contexts = [speech_contexts_element]
config = {"encoding": encoding, "speech_contexts": speech_contexts}
uri = "gs://[BUCKET]/[FILENAME]"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can we use terms like "gs://[YOUR_BUCKET]/[YOUR_FILENAME]" to make crystal clear that it is up to the user to provide these values?

@beccasaurus beccasaurus Dec 18, 2019 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 We could make that a convention for generated sample authors.

In this case, this whole string simply comes from the config:

# Define request message.
request:
- field:   audio.uri
  value:   "gs://[BUCKET]/[FILENAME]"
- field:   config.encoding
  value:   MP3
- field:   config.speech_contexts[0].phrases[0]
  value:   "Fox in socks"
  comment: 'A list of strings containing words and phrases "hints"'
- field:   config.speech_contexts[0].phrases[1]
  value:   "Knox in box"

def sample_analyze_entities():
"""This sample reads and loops over a map field in the response"""

client = language_v1.LanguageServiceClient()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a comment to clarify the role of the client? Some clients generate background resources and should only be created once and used multiple times, while others are just wrappers around HTTP calls. I'm not sure which this client falls under, but I think it would be useful to signal this to users.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the generated clients are just wrappers around gRPC/HTTP calls.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a comment to clarify the role of the client?

Yes!

In lots of languages, there is a 1 line comment above this line. Usually simply:

# Initialize client
client = new library.SomeServiceClient()

But at least it calls attention do it!


TAL at the layout Node.js uses for inspiration (Node PR)

Just look at the comments, I like how it organizes/structures the sample:

function main(displayName = 'This is the default value of the display_name request field') {
  // [START samplegen_basics]
  /**
  * TODO(developer): Uncomment these variables before running the sample.
  */
  // const displayName = 'This is the default value of the display_name request field';

  // Imports the client library
  const {ProductSearchClient} = require('@google-cloud/vision').v1;

  // Instantiates a client
  const productSearchClient = new ProductSearchClient();

  async function sampleCreateProductSet() {

    // Construct request
    const request = {
      parent: formattedParent,
      productSet: productSet,
    };

    // Run request
    const [response] = await productSearchClient.createProductSet(request);

    // The API response represents the created product set
    const productSet = response;
    console.log(`The full name of the created product set: ${productSet.name}`);
  }
  sampleCreateProductSet();
  // [END samplegen_basics]
}

# description: This is the sample description
# usage: python3 samples/v1/samplegen_basics.py [--display_name "This is the default value of the display_name request field"]

# [START samplegen_basics]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python never settled on the canonical format, but there are a few of us working towards doing so (the original PR was currently here, and we have an AI on cleaning it up and creating a sample style guide).

Personally, I have a slight preference to leaving the function signature in (since it would make it easier for me to copy/paste into existing code).


client = vision_v1.ProductSearchClient()

# project = '[PROJECT ID]'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of commenting these out, can we use default values the user can replace?

This way they get compile checked.

def sample_create_product_set(project="YOUR_DISPLAY_NAME"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with this because it's how we do it in node and I'm used to it, but I'd love to hear from more folks as a tie breaker (do we leave it uncommented in java?)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For both Go and Java we switch from commenting them out to using an overriding function to provide defaults. The issue with commented out variables is that there isn't a way to check them vs the compiler, so they often could get changed or updated without anyone noticing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same exact thing in both Ruby and C# as well 😄

We're switching from comments to using method parameter default values.

Love it +💯

language = "en"

# The text content to analyze
content = "Googleplex is at 1600 Amphitheatre Parkway, Mountain View, CA."

This comment was marked as outdated.

language = "en"

# The text content to analyze
content = "Googleplex is at 1600 Amphitheatre Parkway, Mountain View, CA."

This comment was marked as outdated.

client = texttospeech_v1.TextToSpeechClient()

text = "Hello, world!"
input_ = {"text": text}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we instead name this "speech_input" or "sample_input" to avoid having to use the underscore notation? that syntax may not be familiar to folks

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. Not being a Pythonista, myself, I was originally confused by these.

Would it be configurable or... whenever a keyword is used, instead of using a _ suffix, use a prefix instead which scopes the variable to the sample so it's safely not a keyword?

Ack +1

Maybe request_input? Trying to think of a prefix that'll work and make sense in as many cases as possible


client = vision_v1.ProductSearchClient()

# project = '[PROJECT ID]'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with this because it's how we do it in node and I'm used to it, but I'd love to hear from more folks as a tie breaker (do we leave it uncommented in java?)

for key, value in entity.metadata.items():
print(u"{}: {}".format(key, value))

# Loop over just keys:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate?

print(u"{}: {}".format(key, value))

# Loop over just keys:
for the_key, _ in entity.metadata.items():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or better yet,

Suggested change
for the_key, _ in entity.metadata.items():
for key in entity.metadata.keys():

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

w00t

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix both the .keys() and .values() loops!

print(u"Key: {}".format(the_key))

# Loop over just values:
for _, the_value in entity.metadata.items():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or better yet,

Suggested change
for _, the_value in entity.metadata.items():
for value in entity.metadata.values():

@busunkim96 busunkim96 closed this Jul 10, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: yes This human has signed the Contributor License Agreement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants